OpenNext Cloudflare 本地开发中的环境变量边界
问题现象
在本地运行 Next.js 开发服务器时,控制台同时出现了两个看起来互相矛盾的现象:
Using secrets defined in .dev.vars但访问 /api/auth/session 时,Auth.js 仍然报错:
[auth][error] MissingSecret: Please define a `secret`.
GET /api/auth/session 500.dev.vars 中已经写了 AUTH_SECRET,wrangler.toml 中也有同名配置,为什么 Auth.js 还会认为没有 secret?
关键边界
这里的问题不在于 .dev.vars 没有被读取,而在于“谁读取、读到哪里”。
OpenNext Cloudflare 在 next dev 中会通过 initOpenNextCloudflareForDev() 初始化 Cloudflare 本地上下文。这个过程会让 Wrangler/Miniflare 读取 wrangler.toml 和 .dev.vars,所以日志会打印:
Using secrets defined in .dev.vars这表示 Cloudflare 本地运行时能拿到这些变量,典型访问方式是:
import { getCloudflareContext } from "@opennextjs/cloudflare";
const { env } = getCloudflareContext();
const secret = env.AUTH_SECRET;但是 Auth.js 的 MissingSecret 检查默认读取的是 Node 进程环境变量:
process.env.AUTH_SECRET在 next dev 这条链路里,.dev.vars 被读进了 Cloudflare context,不等于被同步进了 process.env。所以会出现:
- Wrangler/OpenNext:我读到了
.dev.vars。 - Auth.js:我在
process.env里没有看到AUTH_SECRET。
这两句话可以同时为真。
为什么容易误判
Using secrets defined in .dev.vars 这句日志很容易让人以为所有库都能读到 .dev.vars 中的变量。
实际不是。它只说明 Wrangler 侧加载了本地 secrets。对于使用 getCloudflareContext().env 的代码,例如 D1、KV、R2 绑定,这通常没有问题。
但很多 Next.js / Node.js 生态库不认识 Cloudflare context,只读取 process.env。Auth.js 就属于这一类。它的 AUTH_SECRET / NEXTAUTH_SECRET 自动推断也是从 process.env 读取。
本项目里的实际链路
本地开发命令:
npm run dev最终进入:
npm run dev -w apps/web
next dev --webpackapps/web/next.config.ts 中调用了:
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
initOpenNextCloudflareForDev();这能让应用代码通过 getCloudflareContext() 拿到 Cloudflare env 和 bindings,但不会自动把 .dev.vars 全量写入 process.env。
可以用 Wrangler API 验证 .dev.vars 是否被 Cloudflare context 读到:
Push-Location apps/web
node -e "import('wrangler').then(async ({ getPlatformProxy }) => { const { env } = await getPlatformProxy({ envFiles: [] }); console.log({ hasAuthSecret: Boolean(env.AUTH_SECRET), nextjsEnv: env.NEXTJS_ENV, trustHost: env.AUTH_TRUST_HOST }); })"
Pop-Location如果输出类似:
Using secrets defined in .dev.vars
{ hasAuthSecret: true, nextjsEnv: 'development', trustHost: 'true' }只能证明 Cloudflare context 有值,不能证明 process.env.AUTH_SECRET 有值。
正确处理方式
如果某个库读取的是 process.env,本地开发时就应该把变量放进 Next.js 会加载的 .env 文件,例如:
apps/web/.env.local内容示例:
AUTH_SECRET=...
AUTH_TRUST_HOST=true.dev.vars 仍然可以保留给 Cloudflare 本地 context 使用:
NEXTJS_ENV=development
AUTH_SECRET=...
AUTH_TRUST_HOST=true但要记住:
.env.local面向next dev和process.env。.dev.vars面向 Wrangler/OpenNext/Cloudflare context。wrangler.toml [vars]面向 Wrangler 配置和部署运行时。
如果不想手动维护两份本地文件,可以在项目里写一个同步脚本,把 wrangler.toml [vars] 或 .dev.vars 同步到 .env.local。这属于项目约定,不是 OpenNext 或 Wrangler 自动完成的行为。
这个判断来自 OpenNext Cloudflare 的环境变量文档:本地开发时,如果要让变量进入 process.env,应使用 Next.js 的 .env 文件,而不是依赖 wrangler 配置或 .dev.vars。Cloudflare Workers 文档则把 .dev.vars 描述为 Wrangler 本地开发 secrets 文件。两者服务的入口不同。
不建议的处理
不建议为了 Auth.js 直接在通用配置里混入 Cloudflare context:
secret: getCloudflareContext().env.AUTH_SECRET这样会把一个本应由 process.env 解决的通用库配置,绑定到 Cloudflare 运行时上下文。除非明确决定 Auth 配置只运行在 Cloudflare 环境中,否则这会让本地、构建、测试和其它运行方式的边界更难理解。
也不建议把开发命令写成很绕的 Node 入口,只为了强行加载 .dev.vars:
node --env-file=.dev.vars ... next dev这种写法虽然能工作,但会让普通的 Next.js 开发命令变得不直观,也容易和团队习惯脱节。
排查清单
遇到 MissingSecret 时,可以按下面顺序判断:
- 确认 Auth.js 实际读取的是
AUTH_SECRET还是NEXTAUTH_SECRET。 - 确认当前命令是
next dev、OpenNext preview,还是 Cloudflare Worker 运行时。 - 如果是
next dev,检查.env.local是否有AUTH_SECRET。 - 如果日志显示
Using secrets defined in .dev.vars,只说明 Cloudflare context 读到了,不代表process.env有值。 - 修改 env 文件后必须重启 dev server,旧 Node 进程不会自动获得新的环境变量。
结论
这次坑的核心不是 secret 没配置,而是配置进入了不同的环境容器。
Using secrets defined in .dev.vars 说明 Wrangler/OpenNext 读到了本地 secrets;Auth.js MissingSecret 说明 process.env.AUTH_SECRET 没有值。两者不冲突。
本地开发时,如果依赖的是 Node/Next 生态库的 process.env,就使用 .env.local。如果依赖的是 Cloudflare bindings 或 getCloudflareContext().env,才使用 .dev.vars / wrangler.toml 这条链路。