先看现象:构建后的环境变量和你以为的不一样
接到这类问题时,我的习惯是先确认对方在哪个环节发现了环境变量不生效。多数时候,问题出在 nuxt.config 里直接写了 if (process.env.NODE_ENV === 'development'),然后在生产构建后执行 nuxt start,发现条件逻辑没按预期走。这里需要解释为什么:
在 Nuxt 3 中,区分开发与生产环境最直接的方式是依赖 `process.env.NODE_ENV`。在 `nuxt.config` 中,你可以通过 `process.env.NODE_ENV === 'development'` 或 `'production'` 来编写条件逻辑。但注意,这个变量在构建时被静态替换,如果你在构建后动态运行服务器,其值可能不准确。例如,使用 `nuxt dev` 时 `NODE_ENV` 为 `development`,而 `nuxt build` 后 `nuxt start` 时则为 `production`。
推荐在 `nuxt.config` 中使用 `runtimeConfig` 来暴露环境变量,而非直接引用 `process.env`。你可以在配置中定义 `publicRuntimeConfig` 和 `privateRuntimeConfig`,并通过 `process.env` 赋值默认值。例如:`publicRuntimeConfig: { apiBase: process.env.NUXT_API_BASE || 'http://localhost:3000' }`。然后在组件中通过 `useRuntimeConfig()` 获取,自动区分环境。
在 Nuxt 3 中,区分开发与生产环境最直接的方式是依赖 process.env.NODE_ENV。在 nuxt.config 中,你可以通过 process.env.NODE_ENV === 'development' 或 'production' 来编写条件逻辑。但注意,这个变量在构建时被静态替换,如果你在构建后动态运行服务器,其值可能不准确。例如,使用 nuxt dev 时 NODE_ENV 为 development,而 nuxt build 后 nuxt start 时则为 production。
也就是说,构建阶段就已经决定了 NODE_ENV 的值,运行时不会重新读取。如果你在构建后改了环境变量(比如在启动脚本中重新设置了 NODE_ENV),nuxt.config 里已经硬编码了构建时的值。
容易误判的两种写法
不少人会把 process.env 直接写在 nuxt.config 顶层,以为每次请求都会重新计算。但实际上 nuxt.config 只执行一次——在构建阶段。以下两种写法都有隐患:
- 在配置顶层用
process.env做条件判断:比如export default defineNuxtConfig({ css: process.env.NODE_ENV === 'development' ? ['dev.css'] : [] }),这个条件只会在构建时生效,运行时不会变化。 - 在配置里引用
process.env作为默认值:比如publicRuntimeConfig: { apiBase: process.env.API_BASE },如果构建时没有设置API_BASE,它就变成undefined,且不会在运行时被覆盖(因为publicRuntimeConfig的值在构建时就被内联到客户端代码中)。
一个常见错误是在 nuxt.config 顶层直接使用 process.env 并认为运行时仍能动态变化。实际上,nuxt.config 在构建阶段执行,所有 process.env 引用都会被替换为当时的值。若需要在运行时动态区分环境,应使用 runtimeConfig 或通过 nitro 配置中的 env 选项。另外,切勿在客户端代码中暴露私密变量,即使通过 privateRuntimeConfig 也不行。
稳妥处理顺序:用 runtimeConfig 兜底
推荐在 nuxt.config 中使用 runtimeConfig 来暴露环境变量,而非直接引用 process.env。你可以在配置中定义 publicRuntimeConfig 和 privateRuntimeConfig,并通过 process.env 赋值默认值。例如:publicRuntimeConfig: { apiBase: process.env.NUXT_API_BASE || 'http://localhost:3000' }。然后在组件中通过 useRuntimeConfig() 获取,自动区分环境。
这里的关键在于:runtimeConfig 的值在构建时会被序列化并合并到 Nitro 的运行时配置中。对于 publicRuntimeConfig,所有客户端代码都能拿到(因为会内联到 JS bundle);而 privateRuntimeConfig 仅在服务端渲染阶段可用。所以处理顺序建议是:
- 先确定变量是否需要在客户端公开(比如 API 基础路径、CDN URL),放在
publicRuntimeConfig中。 - 对于仅服务端使用的密钥(如数据库密码、私密 token),放在
privateRuntimeConfig中。 - 在
nuxt.config中统一为这些变量设置默认值,默认值可以在开发时用.env文件覆盖,生产环境通过系统环境变量传入。
注意:runtimeConfig 中的 process.env 引用也只在构建时求值,但比直接把逻辑写在顶层更灵活,因为 runtimeConfig 的值可以在构建后通过 Nitro 运行时配置覆盖(需要配合 NITRO_ENV 或部署平台的环境变量机制)。
验证方法:前后端分开打印
为了验证环境变量是否按预期生效,可以在开发模式和生产模式下分别打印 useRuntimeConfig() 的内容。在 app.vue 或任意页面中插入 {{ useRuntimeConfig() }},然后对比 nuxt dev 和 nuxt build && nuxt start 的输出。注意生产模式下 publicRuntimeConfig 会内联到客户端代码,而 privateRuntimeConfig 仅服务端可用。
具体操作:
- 开发模式:执行
nuxt dev,打开浏览器查看页面渲染的 JSON 对象。确认publicRuntimeConfig中的值正确。 - 生产模式:先执行
nuxt build,再nuxt start。查看浏览器端打印的内容,对比publicRuntimeConfig是否与构建时环境变量一致。如果使用了privateRuntimeConfig,可以写一个简单的 server API(比如server/api/config.ts)返回useRuntimeConfig(),通过 curl 或浏览器访问确认服务端值。
如果发现值不对,检查三点:
- 构建时是否设置了对应的环境变量(比如
NUXT_API_BASE)。 .env文件是否放在了项目根目录(仅开发时自动加载,生产构建不会读取.env,需手动设置系统环境变量)。- 是否在
nuxt.config中错误地覆盖了runtimeConfig的默认值。
回滚与风险:公共变量的泄露边界
使用 runtimeConfig 时,publicRuntimeConfig 中定义的所有变量都会打包到客户端 bundle 中,因此仅适合存放非敏感的公共配置(如 API 基础路径)。私有变量(如 API Key)必须放在 privateRuntimeConfig 中,并确保只在服务端代码(如 server/api 或 middleware)中调用。同时,注意 .env 文件的优先级:根目录下的 .env 文件会被自动加载,但 nuxt.config 中直接定义的默认值不会覆盖环境变量。
如果误将敏感变量放入了 publicRuntimeConfig,回滚方法很简单:重新构建前将其移到 privateRuntimeConfig,并检查客户端代码中是否有任何引用。另外,生产环境中如果发现 runtimeConfig 的值不对,可以临时通过系统环境变量覆盖(比如设置 NUXT_PUBLIC_API_BASE 或 NUXT_API_BASE 取决于命名规则),但注意这需要重新构建或重启服务(取决于部署平台对 Nitro 运行时配置的支持程度)。
后续维护:保持配置与部署环境同步
项目稳定后,建议把 runtimeConfig 的默认值写清楚,并维护一份 .env.example 文件列出所有需要的变量及其用途。在 CI/CD 流程中,构建阶段注入环境变量时,确保 publicRuntimeConfig 对应的变量名与 nuxt.config 中引用的 process.env 名称一致(大小写敏感)。另外,如果需求是在运行时动态切换环境(比如多租户),单靠 runtimeConfig 不够,需要结合 Nitro 的 middleware 或自定义插件,这已经超出本次排查范围。
总结一句:先把 nuxt.config 中的条件判断和 process.env 引用替换成 runtimeConfig,然后分别验证开发和生产两种模式下的打印结果,最后确认敏感变量没有被泄露。这套流程能覆盖 90% 的环境变量配置问题。