Astro v6 Node Adapter后端的环境变量设置(v5 到 v6 的一个 breaking change)

这其实是Astro v6的一个改动,Changed: import.meta.env values are always inlined

In Astro 5.13, the experimental.staticImportMetaEnv flag was introduced to update the behavior when accessing import.meta.env directly to align with Vite’s handling of environment variables and ensures that import.meta.env values are always inlined.

In Astro 5.x, non-public environment variables were replaced by a reference to process.env. Additionally, Astro could also convert the value type of your environment variables used through import.meta.env, which could prevent access to some values such as the strings “true” (which was converted to a boolean value), and “1” (which was converted to a number).

Astro 6 removes this experimental flag and makes this the new default behavior in Astro: import.meta.env values are always inlined and never coerced.

所以下面这篇文章讨论的前提,都是Astro v6。

Astro官方文档里有关于环境变量的章节,Using environment variables,里面有个例子:

1
2
3
4
5
// When import.meta.env.SSR === true
const data = await db(import.meta.env.DB_PASSWORD);

// When import.meta.env.SSR === false
const data = fetch(`${import.meta.env.PUBLIC_POKEAPI}/pokemon/squirtle`);

它主要使用Vite的环境变量,Env Variables and Modes,但这里其实就有个坑点,那就是这种方式的值会在build的时候被替换成实际的值,而不是在运行时动态获取,这意味着:

  • 在build的时候就需要提供这个环境变量,不然无法替换;
  • 在运行时无法改变这个变量,因为他已经被写入了构造物里。

假设你在代码里写了:

1
const db = drizzle(import.meta.env.DATABASE_URL);

那么在build时提供了系统环境变量或者.env的时候,且值是 mysql://user:password@localhost:3306/db,它最终的dist会变成(也就是直接inline替换了):

1
const db = drizzle("mysql://user:password@localhost:3306/db");

如果是用node adapter或者其他一些 adapter,对于这种敏感变量,最好不要用这种方式注入,而是继续使用process.env。此外 Astro 也提供了一套astro:env的能力来兼容各类 adapter,不过一般情况下用process.env也足够了。

但是如果你改成用process.env,Astro会处理它自己的import.meta.env,但不会帮你把.env自动变成系统环境变量(也就是不做任何处理的话,process.env无法获取.env定义的内容)。
生产环境用Docker之类的问题不大,本来就应该通过环境变量的方式注入到容器里;本地dev时最简单的做法就是手动用dotenv,例如直接写到astro.config.mjs(也可以选择写在middleware.ts,毕竟只要有请求过来他就会执行,但是不太推荐)里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// @ts-check
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";

import react from "@astrojs/react";

import node from "@astrojs/node";
import "dotenv/config"; // <- 添加这一行

// https://astro.build/config
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},

integrations: [react()],

adapter: node({
mode: "standalone",
}),
});

最后还是要提醒一下,import "dotenv/config"解决的是当前开发/构建进程读取.env的问题,不等于你部署后的dist/server/entry.mjs在运行时也会自动加载 .env。真正到了 Node adapter 的生产运行时,还是应该由宿主环境显式提供这些 process.env