首页 / Astro 教程 / 环境变量

Astro 教程

环境变量

本教程共 56 篇 · 第 35 篇 · 更新于 2026-08-07 · 约 9 分钟阅读

AstroAstro 教程环境变量import.meta.envastro:envPUBLIC_

本节目标:学会把密钥、接口地址这类配置放进环境变量,既安全又方便地在开发和生产之间切换。

你写的网站,经常要连一些”外部东西”:数据库、第三方接口、支付密钥。这些连接信息有两个特点:一来不能写死在代码里公开出去,二来开发环境、测试环境、生产环境的值往往不一样。比如开发时用测试数据库,上线时用真实数据库。**环境变量(Environment Variables)**就是专门解决这个问题的——你把这类配置放在代码之外,运行时再读取。

Astro 里的两套机制

Astro 在这方面给你两套东西。

第一套是沿用 Vite 内置的环境变量支持。Vite 是 Astro 底层的构建工具,它内置了处理环境变量的能力。你写的变量会在构建时被静态替换进代码。这一套用 import.meta.env 来读。

第二套是 Astro 自己的 astro:env API。它让你用一个”模式(schema)“来声明变量,从而获得类型安全:编辑器能提示、写错会报警,还能明确区分”哪些变量能在浏览器用、哪些只能在服务器用”。

Note

一条铁律:所有环境变量在服务端代码里都能用;但只有带 PUBLIC_ 前缀的,才能在浏览器端代码里用。原因很简单——浏览器代码对用户是透明可见的,密钥绝不能发到浏览器。

用 .env 文件定义变量

最常见的写法,是在项目根目录建一个 .env 文件,把变量写进去:

# 这个只在服务器端可用!
DB_PASSWORD="foobar"

# 这个到处都能用!
PUBLIC_POKEAPI="https://pokeapi.co/api/v2"

规则很清楚:

  • SECRET_PASSWORD 这种没有 PUBLIC_ 前缀的,只能服务端读(import.meta.env.SECRET_PASSWORD)。
  • PUBLIC_ANYBODYPUBLIC_ 前缀的,服务端和浏览器端都能读(import.meta.env.PUBLIC_ANYBODY)。

你还可以用文件名区分环境,比如 .env.production.env.development,或者自定义名字 .env.testing.env.staging。这样不同场景加载不同的变量集。

默认情况下,astro devdevelopment 模式,astro buildproduction 模式。想临时切到别的模式,可以加 --mode 参数:

# 用 staging 环境的接口启动开发服务器
npm run astro dev -- --mode staging

# 用 testing 环境的接口构建
npm run astro build -- --mode testing

在代码里读取变量

Astro 推荐用 import.meta.env 来读变量,而不是 Node 常见的 process.env

// 服务端(import.meta.env.SSR === true)时,能读密钥
const data = await db(import.meta.env.DB_PASSWORD);

// 浏览器端(import.meta.env.SSR === false)时,只能读 PUBLIC_ 开头的
const data = fetch(`${import.meta.env.PUBLIC_POKEAPI}/pokemon/squirtle`);

除了你自定义的,Astro 还自带几个默认变量:

  • import.meta.env.MODE:当前模式,astro dev 时是 developmentastro build 时是 production
  • import.meta.env.PROD:生产模式为 true,否则 false
  • import.meta.env.DEV:开发模式为 true,和 PROD 永远相反。
  • import.meta.env.BASE_URL:站点部署的基础路径,由配置里的 base 决定。
  • import.meta.env.SITE:配置里 site 选项的值。
Tip

想在编辑器里对自定义变量有自动提示?在 src/env.d.ts 里扩展 ImportMetaEnv 接口即可。不过这只对 PUBLIC_ 开头的自定义变量做提示比较稳妥。

// src/env.d.ts
interface ImportMetaEnv {
  readonly DB_PASSWORD: string;
  readonly PUBLIC_POKEAPI: string;
}
interface ImportMeta {
  readonly env: ImportMetaEnv;
}

在 Astro 配置文件里读变量

有个容易踩的坑:配置文件 astro.config.mjs 比其它文件更早执行,所以在里面不能import.meta.env 去读 .env 里的变量。

如果你要在配置里读环境变量,得用 process.env,或者 Vite 的 loadEnv 助手手动加载:

// astro.config.mjs
import { loadEnv } from "vite";

const { SECRET_PASSWORD } = loadEnv(process.env.NODE_ENV, process.cwd(), "");

用 astro:env 做类型安全

如果你希望变量不仅有值,还”有据可查、有类型、有校验”,就用 astro:env。先在配置里声明一个 schema(模式):

// astro.config.mjs
import { defineConfig, envField } from "astro/config";

export default defineConfig({
  env: {
    schema: {
      API_URL: envField.string({ context: "client", access: "public", optional: true }),
      PORT: envField.number({ context: "server", access: "public", default: 4321 }),
      API_SECRET: envField.string({ context: "server", access: "secret" }),
    }
  }
});

这里每个变量都标了 context(client 还是 server)和 access(public 还是 secret)。声明之后,从对应的模块导入就行:

---
import { API_URL } from "astro:env/client";
import { API_SECRET } from "astro:env/server";

const data = await fetch(`${API_URL}/users`, {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_SECRET}`
  }
});
---

变量分成三类,记住就好:

  • 公开的客户端变量:进浏览器和服务器的包,从 astro:env/client 导入。
  • 公开的服务器变量:只进服务器包,从 astro:env/server 导入。
  • 秘密的服务器变量:不进任何包,只在 astro:env/server 里读取,最安全。

支持的数据类型有四种:stringnumberenumboolean。你还能加 optionaldefault 等约束。注意:只要从 astro:env/server 导入任何东西,所有的 secret 都会被校验——哪怕你没用到那个变量。构建时可能需要给个占位的假值来通过校验。

Tip

想动态拿某个没写在 schema 里的密钥?用 astro:env/server 导出的 getSecret("FOO") 即可,返回字符串或 undefined

临时在命令行里传变量

不想写进 .env 文件时,也能在启动命令前直接带上变量,只对这一次运行生效:

PUBLIC_POKEAPI=https://pokeapi.co/api/v2 npm run dev

PUBLIC_ 开头的会进浏览器包,其它只在服务端可见。这种方式适合临时切换接口、本地调试——不改文件,也不提交到仓库,干净利落。

astro:env 的局限

astro:env 是个”虚拟模块”,只能在 Astro 的语境里用:中间件、路由、端点、组件、模块都行。但在 astro.config.mjs<script> 脚本里用不了,那种情况只能退回 process.env

默认变量能派什么用场

Astro 自带的几个默认变量,日常很实用。比如根据开发还是生产,切换要连的接口地址:

const apiBase = import.meta.env.DEV
  ? "http://localhost:3000/api"
  : "https://api.my-site.com";

又比如根据 import.meta.env.SITE 拼出站内绝对链接,或根据 BASE_URL 处理带子路径部署的情况。这些不用你自己定义,开箱就有。

让密钥在启动时就被校验

astro:env 时,所有 secret 默认在”第一次从 astro:env/server 导入任何东西”时就被校验。如果你希望更严格——项目一启动就校验密钥是否齐全,可以在配置里打开:

// astro.config.mjs
export default defineConfig({
  env: {
    validateSecrets: true,
    schema: {
      API_SECRET: envField.string({ context: "server", access: "secret" }),
    }
  }
});

打开后,缺失的 secret 会在启动阶段就报错,而不是等到某次请求才暴露,便于尽早发现问题。

Note

secret 默认不会打进最终产物,最安全。但校验失败可能卡住构建,CI 里记得给足占位变量。

两套机制怎么选

import.meta.envastro:env 不是二选一,而是「粗细两档」。

普通项目、变量不多,用 import.meta.env 直接读 .env 里的变量就够了,零配置、上手快。等你开始关心「哪个变量能进浏览器、哪个绝对不能」「变量写错了能不能在编辑器里就报警」「secret 缺失能不能在启动时就报错」,再上 astro:env 的 schema 声明也不迟。

一个实用建议:把真正的密钥(数据库密码、第三方 token)只放在 astro:env/server.env 里不带 PUBLIC_ 前缀的变量中;只有本就打算公开给前端的东西(比如某个公共接口的基础地址)才加 PUBLIC_ 前缀。把这两类分清楚,安全上的坑能少一大半。

小结

环境变量把”会变的配置”从代码里抽出来。普通用法是 .env 文件加 import.meta.env 读取,记住 PUBLIC_ 前缀的才能进浏览器。进阶用法是 astro:env,用 schema 声明变量,拿到类型安全和 client/server 的清晰划分。配置文件里要读变量得用 loadEnvprocess.env

下一章是重点:按需渲染(SSR / 混合模式),它决定了你的页面到底在什么时候生成。把密钥收进环境变量,是走上按需渲染前必须先做好的功课。