Next.js结合自定义Express服务器:如何区分服务端与客户端环境?
优雅处理Next.js服务端/客户端API地址差异的方案
我完全懂你这种感受——直接写typeof window === 'undefined'虽然能解决问题,但总感觉有点“硬编码判断”的hack感,不够优雅。结合你用Next.js+自定义Express容器的场景,给你几个更干净的实现方式:
方案一:通过环境变量+封装工具函数隔离判断
把地址判断逻辑封装到统一的工具函数里,业务代码完全不用关心环境差异:
先在项目根目录的环境变量文件里配置:
- 服务端专用API地址(不会暴露给客户端):
API_URL=http://localhost:3000 - 客户端专用API地址(带
NEXT_PUBLIC_前缀,客户端可见):NEXT_PUBLIC_API_URL=https://mysite.com
- 服务端专用API地址(不会暴露给客户端):
封装一个通用的fetch工具函数:
// utils/fetchData.js export const fetchData = async (endpoint) => { // 只在工具函数里做一次环境判断,业务代码不用重复写 const baseUrl = typeof window === 'undefined' ? process.env.API_URL : process.env.NEXT_PUBLIC_API_URL; const response = await fetch(`${baseUrl}${endpoint}`); if (!response.ok) throw new Error('请求失败'); return response.json(); };
之后在业务代码里直接调用fetchData('/api/posts')就行,不用再管是服务端还是客户端。
方案二:用Express反向代理统一API路径(最推荐)
这个方案彻底消除环境判断,不管服务端还是客户端都用相同的路径,由Express帮你转发请求:
- 先安装代理依赖:
npm install http-proxy-middleware
- 在你的Express服务器代码里添加代理配置:
// server.js const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const next = require('next'); const dev = process.env.NODE_ENV !== 'production'; const nextApp = next({ dev }); const handle = nextApp.getRequestHandler(); const app = express(); // 把所有/api开头的请求代理到内部localhost地址 app.use('/api', createProxyMiddleware({ target: 'http://localhost:3000', // 你的内部服务地址 changeOrigin: true, })); nextApp.prepare().then(() => { app.all('*', (req, res) => handle(req, res)); app.listen(3000, () => { console.log('服务器运行在 http://localhost:3000'); }); });
这样不管是服务端渲染时的fetch,还是客户端的fetch,都直接用/api/xxx的相对路径:
- 服务端请求
/api/xxx时,Express直接转发到内部的http://localhost:3000/api/xxx - 客户端请求
/api/xxx时,会自动拼接成https://mysite.com/api/xxx,然后通过Express反向代理到内部服务
这种方式最干净,业务代码完全不用关心环境差异,也不需要任何判断逻辑。
方案三:利用Next.js路由上下文判断(Pages Router专属)
如果你用的是Pages Router,可以在getServerSideProps或getInitialProps里通过上下文对象判断服务端环境,直接在服务端用内部地址获取数据,客户端则自动用外部地址:
// pages/posts.js export async function getServerSideProps(context) { // 这里肯定是服务端环境,直接用localhost地址 const res = await fetch('http://localhost:3000/api/posts'); const posts = await res.json(); return { props: { posts } }; } // 客户端组件里直接用外部地址,或者用相对路径 export default function Posts({ posts }) { const fetchMorePosts = async () => { const res = await fetch('/api/posts?page=2'); // ... }; return <div>{/* 渲染内容 */}</div>; }
总结
- 最推荐方案二(反向代理):彻底隔离环境差异,代码最简洁
- 如果不想用代理,方案一能把判断逻辑封装起来,避免业务代码冗余
- Pages Router项目可以结合方案三,在服务端数据获取阶段直接用内部地址
内容的提问来源于stack exchange,提问作者Dave Stein
相关产品推荐
相关产品推荐

