本节摘要:密钥、API 地址、功能开关——不同环境(开发/测试/生产)需要不同配置。本节讲清 .env 文件体系、NEXT_PUBLIC_ 前缀的安全边界、next.config.js 的常用配置,以及"哪些配置绝不能暴露给浏览器"。
阅读完本节,你应当能够:
数据库密码、API 密钥、接口地址——写死在代码里:换环境要改代码、密钥进 Git 泄露、多人协作冲突。环境变量的做法:代码里用 process.env.XXX 读取,值放在 .env 文件(不入 Git),部署时按环境注入。
直觉类比:环境变量像"钥匙柜"——代码是"取钥匙的人",.env 是"钥匙柜",不同环境(开发/生产)是不同的钥匙柜。代码不认识具体钥匙,只负责"去柜子里拿"。
💡 关键直觉:NEXT_PUBLIC_ 前缀是"暴露开关"——带这个前缀的变量会内联进浏览器代码(公开);不带前缀的只在服务器可用(保密)。这是 Next.js 环境变量体系最需要记住的一条安全规则。
.env # 所有环境共享(基础配置) .env.development # 开发环境覆盖 .env.production # 生产环境覆盖 .env.local # 本地私有(不入 Git)
# .env DATABASE_URL=postgresql://user:pass@localhost:5432/app NEXT_PUBLIC_API_BASE=https://api.example.com SECRET_KEY=my-secret-key
优先级:.env.local > .env.development/.env.production > .env。
// 服务器组件/Route Handlers —— 所有变量可读 export async function GET() { const dbUrl = process.env.DATABASE_URL; // 服务器端,安全 return Response.json({ dbUrl: !!dbUrl }); } // 客户端组件 —— 只能读 NEXT_PUBLIC_ 前缀 "use client"; export function ClientWidget() { const base = process.env.NEXT_PUBLIC_API_BASE; // 公开变量 return <div>API: {base}</div>; }
安全边界:
| 变量 | 服务器组件 | 客户端组件 | 说明 |
|---|---|---|---|
DATABASE_URL |
✅ | ❌ | 密钥,绝不暴露 |
SECRET_KEY |
✅ | ❌ | 密钥 |
NEXT_PUBLIC_API_BASE |
✅ | ✅ | 公开(API 地址无妨) |
NEXT_PUBLIC_ANALYTICS_ID |
✅ | ✅ | 公开 |
# .gitignore .env.local .env.*.local
纪律:.env 可以提交(放非敏感默认值),.env.local 必须忽略(放真实密钥);仓库里提供 .env.example 模板供新同事复制。
// next.config.ts import type { NextConfig } from "next"; const nextConfig: NextConfig = { images: { remotePatterns: [{ protocol: "https", hostname: "cdn.example.com" }], }, redirects: async () => [ { source: "/old-path", destination: "/new-path", permanent: true }, ], // 运行时环境变量(对 NEXT_PUBLIC_ 注入更可控) env: { CUSTOM_KEY: process.env.CUSTOM_KEY, }, }; export default nextConfig;
常用配置项:
| 配置 | 用途 |
|---|---|
| images.remotePatterns | 允许的远程图片域名 |
| redirects/rewrites | URL 重定向/重写 |
| output: "standalone" | 独立部署产物(Docker 优化) |
| experimental | 实验特性(按版本) |
| env | 显式注入环境变量 |
| 误区 | 现象 | 正解 |
|---|---|---|
| 在客户端用非 PUBLIC 变量 | 值为 undefined | 只读 NEXT_PUBLIC_ 前缀 |
| 密钥提交 Git | 泄露 | .env.local 加 gitignore |
| 改 .env 不生效 | 变量还是旧值 | 重启 dev 服务器(改 .env 需重启) |
| 生产变量缺失 | 页面报错 | 部署平台配置环境变量 |
| 把整个 .env 提交 | 全量泄露 | 只提交 .env.example |
// lib/config.ts —— 统一读取配置(服务器端) export function getServerConfig() { return { databaseUrl: process.env.DATABASE_URL!, secretKey: process.env.SECRET_KEY!, apiBase: process.env.NEXT_PUBLIC_API_BASE!, }; } // 使用(只能在服务器组件/Route Handler/Server Action 中) // app/api/health/route.ts export async function GET() { const { databaseUrl } = getServerConfig(); return Response.json({ db: !!databaseUrl }); }
规则总结:密钥类配置只在服务器侧使用;需要暴露给浏览器的才加 NEXT_PUBLIC_;统一封装读取函数,避免 process.env 散落各处——配置管理清晰、安全边界明确。
关键区分:
注意:NEXT_PUBLIC_ 变量是"构建时"的——改它的值需要重新构建,不是改环境变量就生效。
// lib/config.ts —— 服务器侧配置统一出口 export const serverConfig = { databaseUrl: process.env.DATABASE_URL!, authSecret: process.env.AUTH_SECRET!, }; // lib/public-config.ts —— 客户端可见配置 export const publicConfig = { apiBase: process.env.NEXT_PUBLIC_API_BASE!, analyticsId: process.env.NEXT_PUBLIC_ANALYTICS_ID!, };
一句话:环境变量的纪律是"该公开的显式公开(NEXT_PUBLIC_),该保密的默认保密(无前缀)"——前缀就是安全边界,写代码时先想清楚这个值要不要给浏览器看到。
问:改 .env 后需要重启吗?
需要。Next.js 在启动/构建时读取环境变量(NEXT_PUBLIC_ 构建时内联),运行中修改不生效。重启 dev 或重新 build。
问:NEXT_PUBLIC_ 变量会暴露给所有人吗?
会。它内联进客户端 JS,任何访问者都能在浏览器源码里看到。所以只放"公开信息"(API 地址、分析 ID),绝不放密钥。
问:多个环境怎么管理?
.env(公共)+ .env.development/.env.production(按环境覆盖)+ .env.local(本机私有)。部署平台(Vercel)的环境变量配置优先级更高。
问:数据库地址能放 NEXT_PUBLIC_ 吗?
绝对不能。数据库地址是敏感信息,放无前缀变量,只在服务器侧读取。
问:next.config 的 env 配置和 .env 有什么区别?
next.config 的 env 是"构建时硬编码注入"(每次改都要重新 build);.env 由 Node 运行时加载,更灵活。新项目优先用 .env。
环境变量的安全边界就是 NEXT_PUBLIC_ 前缀——带前缀进浏览器(公开),无前缀只在服务器(保密)。密钥绝不加前缀、.env.local 不入 Git、按环境隔离。配置统一封装(lib/config.ts),让"这个值能不能给浏览器"成为写代码时的第一反应。
验证环境变量的安全边界:在 .env 里定义 SECRET_KEY=abc 与 NEXT_PUBLIC_SITE_NAME=我的站,在服务器组件里打印两者(都能读到),在客户端组件里打印两者(只有 PUBLIC 能读到,另一个为 undefined)。理解"前缀即边界"。
再实践配置中心化:写 lib/config.ts 统一读取服务器配置,lib/public-config.ts 读取公开配置,并在项目各处使用——不再散落 process.env。最后把 .env.local 加入 .gitignore,提供 .env.example 模板,模拟团队协作的配置交接。