Next.js项目构建后如何从服务端向客户端传递动态API地址(基于环境变量)
Next.js项目构建后如何从服务端向客户端传递动态API地址(基于环境变量)
这个需求我太熟悉了——一次构建、多实例部署,每个实例靠环境变量切换API地址,核心就是要让客户端能在运行时动态获取服务端的环境变量值,而不是把值固化在构建产物里。我给你几个经过验证的可行方案,你可以根据自己的场景选:
方案一:在服务端渲染时把配置注入全局window对象
这是最直接的方式,不需要额外的网络请求,客户端加载页面时就能拿到配置。
你可以修改pages/_document.js(如果用App Router的话是app/layout.js服务端组件),在服务端获取环境变量,然后把它注入到HTML的全局脚本里:
代码示例(Pages Router):
// pages/_document.js import Document, { Html, Head, Main, NextScript } from 'next/document'; class MyDocument extends Document { static async getInitialProps(ctx) { const initialProps = await Document.getInitialProps(ctx); // 服务端获取环境变量,映射成对应的API地址 const customerApiUrl = process.env.CUSTOMER === 'customerA' ? 'https://api.customerA.com' : process.env.CUSTOMER === 'customerB' ? 'https://api.customerB.com' : 'https://api.default.com'; return { ...initialProps, customerApiUrl }; } render() { return ( <Html> <Head /> <body> {/* 把API地址注入全局window对象 */} <script dangerouslySetInnerHTML={{ __html: `window.APP_CONFIG = { apiUrl: "${this.props.customerApiUrl}" };`, }} /> <Main /> <NextScript /> </body> </Html> ); } } export default MyDocument;
代码示例(App Router):
// app/layout.js export default function RootLayout({ children }) { // 服务端组件可以直接访问process.env const customerApiUrl = process.env.CUSTOMER === 'customerA' ? 'https://api.customerA.com' : process.env.CUSTOMER === 'customerB' ? 'https://api.customerB.com' : 'https://api.default.com'; return ( <html lang="en"> <body> <script dangerouslySetInnerHTML={{ __html: `window.APP_CONFIG = { apiUrl: "${customerApiUrl}" };`, }} /> {children} </body> </html> ); }
之后客户端任何组件里都可以直接用window.APP_CONFIG.apiUrl获取API地址。这个方案的优势:
- 无需额外请求,页面加载时就拿到配置
- 每次启动服务时环境变量变化,值会自动更新(因为是服务端渲染时动态生成的)
方案二:用Next.js API路由作为配置中间层
这个方案适合需要更灵活配置的场景,比如后续要加更多配置项,或者需要对配置请求做权限验证。
核心思路是:客户端通过相对路径请求自己部署的API路由,这个路由在服务端读取环境变量后返回对应API地址。因为是相对路径,客户端完全不需要知道自己的部署域名——浏览器会自动用当前页面域名发起请求。
步骤:
- 创建API路由文件:
// Pages Router: pages/api/get-config.js export default function handler(req, res) { // 服务端读取环境变量,映射API地址 const customerApiUrl = process.env.CUSTOMER === 'customerA' ? 'https://api.customerA.com' : process.env.CUSTOMER === 'customerB' ? 'https://api.customerB.com' : 'https://api.default.com'; res.status(200).json({ apiUrl: customerApiUrl }); } // App Router: app/api/get-config/route.js export async function GET() { const customerApiUrl = process.env.CUSTOMER === 'customerA' ? 'https://api.customerA.com' : process.env.CUSTOMER === 'customerB' ? 'https://api.customerB.com' : 'https://api.default.com'; return Response.json({ apiUrl: customerApiUrl }); }
- 客户端组件里请求这个路由:
// 客户端组件 import { useEffect, useState } from 'react'; function MyComponent() { const [apiUrl, setApiUrl] = useState(''); useEffect(() => { async function fetchConfig() { const res = await fetch('/api/get-config'); const config = await res.json(); setApiUrl(config.apiUrl); } fetchConfig(); }, []); return <div>当前API地址:{apiUrl}</div>; } export default MyComponent;
这个方案的优势:
- 完全解耦客户端和服务端的配置逻辑,后续修改配置规则只需要改API路由
- 支持复杂场景,比如根据请求头返回不同配置,或者加缓存
方案三:启动前生成静态配置文件
如果你不想用服务端渲染或者API路由,可以在启动Next.js之前,根据环境变量生成一个静态配置文件放到public目录,客户端直接请求这个静态文件。
步骤:
- 写一个生成配置的脚本
generate-config.js:
// generate-config.js const fs = require('fs'); const path = require('path'); // 根据环境变量映射API地址 const customerApiUrl = process.env.CUSTOMER === 'customerA' ? 'https://api.customerA.com' : process.env.CUSTOMER === 'customerB' ? 'https://api.customerB.com' : 'https://api.default.com'; // 生成config.json到public目录 const configContent = JSON.stringify({ apiUrl: customerApiUrl }); fs.writeFileSync(path.join(__dirname, 'public', 'config.json'), configContent); console.log('配置文件生成成功,API地址:', customerApiUrl);
- 修改
package.json的启动脚本,先运行生成脚本再启动Next.js:
{ "scripts": { "start": "node generate-config.js && next start", "build": "next build" } }
- 客户端组件里请求这个静态文件:
// 客户端组件 import { useEffect, useState } from 'react'; function MyComponent() { const [apiUrl, setApiUrl] = useState(''); useEffect(() => { async function fetchConfig() { const res = await fetch('/config.json'); const config = await res.json(); setApiUrl(config.apiUrl); } fetchConfig(); }, []); return <div>当前API地址:{apiUrl}</div>; } export default MyComponent;
这个方案的优势是简单易懂,适合不需要复杂逻辑的场景,但要注意每次启动服务前都要运行生成脚本,部署时要确保启动命令包含这一步。
为什么你之前的尝试没成功?
PUBLIC_NEXT_前缀的变量:Next.js会在构建时把这些变量注入客户端代码,构建后再改环境变量没用——值已经固化在JS文件里了。next.config.js里的env配置:同样是构建时处理的,构建后修改环境变量不会更新客户端拿到的值。- 读取
.txt文件:客户端无法直接读取服务端的文件,只能通过服务端接口或者静态资源的方式获取。
备注:内容来源于stack exchange,提问作者Vlad
相关产品推荐
相关产品推荐

