Next.js 环境变量与 Server-only Secret 边界
解释 Next.js 中服务端环境变量、NEXT_PUBLIC_ 构建时公开变量与 server-only 模块边界,避免 Secret 意外进入客户端 bundle。
#concept
#growing
#frame
#security
#react
[!info] related notes
- 所属 MOC: nextjs-moc
- 前置概念: react-server-vs-client-components
- 并列概念:
- 易混淆概念:
- 关系笔记:
Next.js 环境变量与 Server-only Secret 边界
一句话定义
Next.js 默认把普通环境变量留在服务器;只有显式使用 NEXT_PUBLIC_ 前缀的变量才应进入浏览器可见代码,并且这类值通常在构建阶段被内联进客户端 bundle。
两类变量
DATABASE_URL
AUTH_SECRET
API_KEY
→ server-only
NEXT_PUBLIC_ANALYTICS_ID
NEXT_PUBLIC_SITE_URL
→ public client config
最重要的规则:
NEXT_PUBLIC_
= 可以公开给任何访问网页的人
≠ “前端也能安全保存 Secret”
为什么 NEXT_PUBLIC_ 要谨慎
客户端 bundle 是用户可以下载和检查的代码,所以一旦变量进入浏览器:
public variable
→ assume fully visible
不能把数据库密码、Auth Secret、第三方私钥等放进去。
另外,公开变量通常在 next build 时被内联,因此构建完成后再修改部署环境里的值,不一定能改变已经生成的客户端 JS。
Server-only 模块
即使普通 env 默认不会被暴露,也应该防止含 Secret 的数据模块被误导入 Client Component:
import 'server-only'
典型 server-only 模块包括:
- 数据库访问
- Auth secret
- 私有 API client
- Repository
- 内部 Service client
这样可以把“不要在客户端用”从团队约定提升为构建期边界。
Runtime vs Build-time
Server runtime env
→ 服务端请求执行时读取
NEXT_PUBLIC_* client env
→ 常见情况在 build 时固化进 bundle
这意味着同一个 Docker image 跨环境推广时,公开配置的处理方式与 server runtime secret 不一定相同。
与 Server Component 的关系
Server Component 可以读取服务器环境变量,但不能把 Secret 作为 props 传给 Client Component:
Server knows secret
≠ browser should receive secret
正确做法是服务端使用 Secret 完成访问,再只返回 UI 真正需要的数据。
最短记忆
no prefix → server by default
NEXT_PUBLIC_ → browser-visible
browser-visible → not a secret
server-only → protect module boundary
server can read secret ≠ client can receive secret