环境变量
本教程共 56 篇 · 第 35 篇 · 更新于 2026-08-07 · 约 9 分钟阅读
本节目标:学会把密钥、接口地址这类配置放进环境变量,既安全又方便地在开发和生产之间切换。
你写的网站,经常要连一些”外部东西”:数据库、第三方接口、支付密钥。这些连接信息有两个特点:一来不能写死在代码里公开出去,二来开发环境、测试环境、生产环境的值往往不一样。比如开发时用测试数据库,上线时用真实数据库。**环境变量(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_ANYBODY带PUBLIC_前缀的,服务端和浏览器端都能读(import.meta.env.PUBLIC_ANYBODY)。
你还可以用文件名区分环境,比如 .env.production、.env.development,或者自定义名字 .env.testing、.env.staging。这样不同场景加载不同的变量集。
默认情况下,astro dev 用 development 模式,astro build 用 production 模式。想临时切到别的模式,可以加 --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时是development,astro 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里读取,最安全。
支持的数据类型有四种:string、number、enum、boolean。你还能加 optional、default 等约束。注意:只要从 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 会在启动阶段就报错,而不是等到某次请求才暴露,便于尽早发现问题。
Notesecret 默认不会打进最终产物,最安全。但校验失败可能卡住构建,CI 里记得给足占位变量。
两套机制怎么选
import.meta.env 和 astro: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 的清晰划分。配置文件里要读变量得用 loadEnv 或 process.env。
下一章是重点:按需渲染(SSR / 混合模式),它决定了你的页面到底在什么时候生成。把密钥收进环境变量,是走上按需渲染前必须先做好的功课。