OpenNext Cloudflare 本地开发中的环境变量边界
太阳作者太阳
原创内容采用 CC-4.0 协议发布,转载请注明出处
OpenNextCloudflareNext.jsAuth.js

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_SECRETwrangler.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 --webpack

apps/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 devprocess.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 时,可以按下面顺序判断:

  1. 确认 Auth.js 实际读取的是 AUTH_SECRET 还是 NEXTAUTH_SECRET
  2. 确认当前命令是 next dev、OpenNext preview,还是 Cloudflare Worker 运行时。
  3. 如果是 next dev,检查 .env.local 是否有 AUTH_SECRET
  4. 如果日志显示 Using secrets defined in .dev.vars,只说明 Cloudflare context 读到了,不代表 process.env 有值。
  5. 修改 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 这条链路。

参考资料