Cloudflare Worker + Next.js 使用环境变量最佳实践(2026终极版)

随着 Vibe coding 兴起,我们有越来越多的项目基于 Next.js + OpenNext 构建,部署在 Cloudflare Workers 上。这套方案性能强悍、成本极低,堪称目前全栈开发的“版本答案”。

但是,很多同学(包括我自己)从 Vercel、Docker 或者 VPS 迁移过来时,都会掉进同一个坑里:环境变量怎么不生效? 或者为什么本地好好的,部署上去就是 undefined?甚至,怎么部署又失败了,为啥访问不到数据库?

今天我们就借着手里这个实战项目(它其实是个典型的反例),来彻底搞清楚 Cloudflare Worker 环境下变量配置的门道。

反例教材:看起来很简单

{
  "name": "app-worker",
  "env": {
    "prod": {
      "vars": {
        "NEXT_PUBLIC_API_URL": "<https://api.example.com>", // ☠️ 以为配在这里就行了?天真!
        "DB_HOST": "postgres.prod.internal"
      },
      "secrets": ["API_SECRET_KEY"]
    }
  }
}

来看看我们项目里的 wrangler.jsonc 配置(这是一个典型的错误示范):

很多人(比如我)的直觉是:“既然 Next.js 需要这些变量,又有这个配置文件,那我就把它们全都配在这里,Wrangler 肯定会帮我处理好的。”

错!大错特错……

两个核心维度

要搞定环境变量,你必须先明确两个核心维度,如果不区分清楚,你的环境变量配置就只能“碰运气”。

构建时 (Build Time) vs 运行时 (Runtime)

构建时,环境变量通过 GUI 配置

  1. 编译打包代码
  2. 预渲染

运行时,环境变量由 vars + secrets 组成,可以本地配置,也可以线上配置

  1. Worker

服务端 (Server-side) vs 客户端 (Client-side)

服务端,可以访问所有变量

  1. Server component
  2. Server action
  3. API

客户端,只能访问构建时就存在的 NEXT_PUBLIC_*

  1. HTML + JS,通常是静态

💣 坑一:构建时 (Build Time) 的缺失

Next.js 的构建机制决定了 NEXT_PUBLIC_ 变量的行为:

  • 所有以 NEXT_PUBLIC_ 开头的变量,在执行 next build 时,会被直接替换成字符串常量并打包进客户端的 JS 文件里。
  • 也就是说,构建时环境变量必须预先定义在“构建 > 变量”里,或者在创建 worker 时,在“高级设置 > 变量”里设置

当你运行 npm run build 时,此时 Cloudflare Worker 还没启动,Wrangler 的配置也没生效。Next.js 的编译器自然看不到 wrangler.jsonc 里的内容,也看不到 .dev.vars

后果

编译后的客户端代码里,process.env.NEXT_PUBLIC_API_URL 会被编译成 undefined。用户打开浏览器,请求发不出去,控制台一片红。

💣 坑二:构建完毕之后的预渲染

Next.js 自带多种渲染模式:

  1. CSR:客户端渲染,前后端分离后常见的模式
  2. SSR:服务器端渲染,在后端渲染完页面之后把 HTML 直接发给浏览器,有利于用户体验和 SEO
  3. ISR:渐增式渲染,页面渲染后,将 HTML 作为缓存保存起来,一段时间内不需要重复渲染
  4. Pre-rendering:预渲染,把静态页面和高频访问页面在构建时渲染好,运行时就只看缓存,性能最优

于是,构建完成之后,Next.js 会自动帮我们启动第四步,也就是预渲染首页、sitemap 和其他一些明确可以缓存的页面。这个时候,wrangler.jsonc 里的变量也没有生效

后果

预渲染的执行环境在构建运行时,但是也要启动服务器,但但是没有 wrangler.jsonc 定义的变量……😵‍💫😵‍💫……所以不是 NEXT_PUBLIC_ 的环境变量也要定义在构建时变量里。否则,可能连不上数据库、访问不到 API,导致预渲染失败,继而部署失败。

💣 坑三:运行时 (Runtime) 的混淆

Cloudflare Worker 和传统的 Node.js 服务器不同。

  • Traditional Node (如 Vercel / Docker) 里,环境变量通常在启动进程前注入到 process.env
  • Cloudflare Worker 里,环境变量是作为对象绑定 (Bindings) 挂载在 env 参数上的。
  • 虽然 OpenNextnodejs_compat 帮我们做了很多兼容工作,试图把 env 映射回 process.env,但这仅限于服务端运行时

✅ 正确姿势:分而治之

要在 Cloudflare Worker + Next.js 体系下玩转环境变量,你需要把它们拆开处理。

1. 搞定构建时 (Build Time)

目标:确保 next build 能读到 NEXT_PUBLIC_ 变量。
做法

  • 本地开发:老老实实写 .env 文件(如果在 .dev.vars 里写了 NODEJS_ENV=development,那就要写 .env.development)。
  • CI/CD (GitHub Actions / Cloudflare Worker Build)必须在构建时环境变量设置里,把所有 NEXT_PUBLIC_ 变量填一遍。同时,预渲染所需的环境变量也要定义。
# 必须在构建环境存在的变量
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_ANALYTICS_ID=xyz123

# 预渲染时所需的变量,比如首页需要读取数据库
DATABASE_URL=mysql:xxxx

2. 搞定运行时 (Runtime)

目标:让服务端代码 (API Routes, Server Components) 能读到 Vars 和 Secrets。
做法:使用 wrangler.jsonc (或 wrangler.toml) 配置。

这就是 wrangler 发挥作用的地方。这里的变量只会在 Worker 跑在边缘节点上时存在,并且只有服务端代码能读到

{
  "env": {
    "prod": {
      // ✅ 这里的变量是给 Server 用的
      "vars": {
        // 其实这里甚至不需要配 NEXT_PUBLIC_...,除非你在 Server 端也用了它
        // 但为了统一管理,通常也会保留一份
        "NEXT_PUBLIC_API_URL": "<https://api.example.com>",
        "INTERNAL_CONFIG": "some-value"
      },
      // ✅ 敏感信息放 secrets,不要明文写在 vars 里
      "secrets": ["DATABASE_PASSWORD", "OPENAI_API_KEY"]
    }
  }
}

通常来说,不能暴露给前端的环境变量都会定义在这里。

3. 本地开发必备:.dev.vars.env(.development)

你可能会问:“wrangler.jsonc 里只能写明文的 vars,那 secrets 怎么办?总不能把 API Key 提交到 GitHub 吧?”

这时候就需要 .dev.vars 文件出场了。

  • 作用:专门用于本地开发 (npm run dev) 时的 Secrets 注入。
  • 格式:和 .env 一样,KEY=VALUE
  • 注意千万不要提交到 git! (记得加 .gitignore)
# 本地开发用的 Secrets
OPENAI_API_KEY=sk-proj-123456
DATABASE_PASSWORD=secret-password

当你运行 wrangler devnext dev (通过 OpenNext 适配器) 时,Wrangler 会自动读取这个文件,并把它挂载到 env 上。这样你就不用在 wrangler.jsonc 里写假的 secrets。

如果某些环境变量只有前端会用,那么就可以用 .env 来存放,跟其他框架的开发体验一致。

4. 极致体验:类型安全 (Type Safety)

在 TypeScript 项目里,最爽的莫过于输入 env. 之后,编辑器自动提示所有的变量名。Cloudflare 提供了官方支持来实现这一点。

步骤

  1. 运行命令生成类型定义:
    npx wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts
    
  2. 这个命令会扫描你的 wrangler.jsonc.dev.vars,自动生成 cloudflare-env.d.ts 文件,里面定义了 CloudflareEnv 接口。
interface CloudflareEnv {
    KV_Company: KVNamespace;
    DB: D1Database;
    NEXT_PUBLIC_API_URL: string;
    OPENAI_API_KEY: string;
}
  1. 在代码里直接使用这个类型(配合 OpenNext 或 Remix 等框架的 Loader/Action):
    import { getCloudflareContext } from "@opennextjs/cloudflare";
    
    export async function GET(request: Request) {
      const { env } = await getCloudflareContext();
      // 这里的 env 就是 CloudflareEnv 类型,有自动补全!
      console.log(env.NEXT_PUBLIC_API_URL);
    }
    

5. 代码怎么写?

在 Next.js + OpenNext 环境下,你可以像平时一样写代码,但心里要有数:

// ✅ 场景 A:客户端组件 (Client Component)
// 这个值是在 Build Time 被“烧录”进来的。
// 如果构建时没给 env,这就是 undefined,不管你 wrangler 里配没配。
console.log(process.env.NEXT_PUBLIC_API_URL);

// ✅ 场景 B:服务端组件 (Server Component / API Route)
// 这个值是在 Runtime 动态获取的。
// OpenNext 会帮我们从 Worker 的 env 注入到 process.env
export async function GET() {
  // 这里能读到 wrangler secrets
  const apiKey = process.env.OPENAI_API_KEY;

  if (!apiKey) {
    throw new Error("Missing API Key! Check your wrangler secrets!");
  }

  return Response.json({ status: "ok" });
}

// 但是,考虑到类型安全、通常我们还要用 bindings,更推荐 getCloudflareContext()
export async function GET() {
  const { env } = getCloudflareContext();
  const apiKey = env.OPENAI_API_KEY;
  await env.KV.get(key);
  return Response.json({ status: "ok" });
}

6. 高级技巧:环境隔离与 Secrets 管理

除了以上几点,还有几个非常重要的“潜规则”,也是新手经常踩坑的地方。

🔒 规则一:Secrets 不会被覆盖 (Secrets Persistence)

很多同学担心部署时 wrangler.jsonc 里没有写 Secrets 的值(出于安全原因),会不会把线上已经配置好的 Secrets 给覆盖成空?
答案是:不会。wrangler deploy 只更新代码和 vars。Secrets 是存储在 Cloudflare 的加密保险箱里的,只要你不显式地去删除或更新它,它就一直都在。

🧬 规则二:Env 不会继承 (No Inheritance)

这是一个反直觉的设计:Environment 配置之间是互不继承的
如果你在 wrangler.jsonc 的最外层写了一堆通用配置,然后在 [env.production] 里只写了差异部分……
恭喜你,你的 Production 环境会丢失所有最外层的配置!

正确做法
在每个 Env (dev, staging, prod) 里,老老实实把所有变量重新写一遍。虽然看起来冗余,但能保证配置的确定性,避免缺乏隐式继承带来的诡异 Bug。

类似的还有各种 bindings,很难用,但是没办法。

🛡️ 规则三:如果没有指定环境,dev 环境不会默认生效

如果你在 wrangler.jsonc 配置了多个环境,比如 dev,staging,prod。但是启动开发环境的时候没有指定环境,那么 dev 环境不会默认生效。

需要在 next.config.ts 里指定环境:

initOpenNextCloudflareForDev({
  environment: 'dev',
  configPath: './wrangler.jsonc',
});

🏆 最佳实践总结

说了这么多,最后送大家一份 Cloudflare Worker 环境变量完全指南

  1. Build Time 分离:凡是 NEXT_PUBLIC_ 开头的变量,建议在构建时环境变量里配置。这样构建时 Next.js 会把它们注入生成后的代码,有助于 Tree-shaking,改进执行效率。
  2. Runtime 显式声明:服务端用的变量,全部写在 wrangler.jsoncvars 里。
  3. Build Time 预渲染:两边都写,写两遍
  4. Secrets 隐式管理:敏感信息(API Key, Password)必须用 wrangler secret put 上传,或者在 Worker 的设置面板里添加。本地开发时,使用在 .dev.vars
  5. 环境完全隔离:为 devprod 准备两套完全独立的资源 ID(KV, R2, D1),防止数据污染。
  6. 配置:所有 Env 的配置必须全量复制,不要依赖继承。
  7. 类型安全:用 wrangler types 生成类型定义,配合 TS 使用,使用 getCloudflareContext() 获取之后使用 env.XXX
  8. **预渲染的服务器端:**使用 process.env.XXX

遵循这套最佳实践,你的 Cloudflare Worker 项目就能稳如磐石,既享受 Serverless 的低成本,又拥有企业级的稳定性。
希望这篇文章能帮你彻底终结环境变量配置的噩梦!

觉得文章有帮助?

如果我的分享对你有所启发,欢迎通过赞助来支持我持续创作。

❤️ 赞助我

评论