NextJS如何在_document.js/_app.js中可靠获取主机名并请求对应API数据
可靠实现NextJS多域名动态API请求方案
针对你遇到的Document.getInitialProps中req不稳定的问题,核心原因是该方法在**客户端渲染场景(如客户端路由跳转)**下不会获取到服务端的req对象,导致无法稳定拿到域名。以下是几种可行的可靠解决方案:
方案一:在_app.js中使用getServerSideProps(Pages Router推荐)
getServerSideProps会在每次请求的服务端阶段执行,无论首次加载还是客户端路由跳转,都能稳定获取到服务端的req对象,从而拿到当前访问域名。
实现步骤:
- 在
pages/_app.js中定义getServerSideProps,请求API获取站点数据后传递给应用组件 - 可选:将站点数据存入React Context,方便全局组件访问
// pages/_app.js import { createContext, useContext } from 'react'; // 创建全局站点数据Context const SiteContext = createContext({}); export function useSiteData() { return useContext(SiteContext); } function MyApp({ Component, pageProps, siteData }) { return ( <SiteContext.Provider value={siteData}> <Component {...pageProps} /> </SiteContext.Provider> ); } MyApp.getServerSideProps = async (context) => { // 服务端稳定获取主机名 const host = context.req.headers.host; // 提取纯域名(去除www前缀和端口号) const domain = host.split(':')[0].replace(/^www\./, ''); // 安全请求API(编码域名避免特殊字符问题) const apiRes = await fetch( `http://api.com/websitedata?url=${encodeURIComponent(domain)}` ); const siteData = await apiRes.json(); return { props: { siteData } }; }; export default MyApp;
使用示例:
在任意页面组件中通过useSiteData获取数据:
// pages/index.js import { useSiteData } from './_app'; export default function Home() { const { name, logo } = useSiteData(); return ( <div> <h1>{name}</h1> <img src={logo} alt="Site Logo" /> </div> ); }
方案二:使用Middleware(NextJS 12+ 通用方案)
Middleware能在请求到达页面之前统一处理,适合需要全局拦截所有请求的场景,可将站点数据通过响应头或Cookie传递给页面。
实现步骤:
- 项目根目录创建
middleware.js - 在Middleware中获取域名、请求API,将数据存入响应头
// middleware.js import { NextResponse } from 'next/server'; export async function middleware(request) { const host = request.headers.get('host'); const domain = host.split(':')[0].replace(/^www\./, ''); // 请求API获取站点数据 const apiRes = await fetch( `http://api.com/websitedata?url=${encodeURIComponent(domain)}` ); const siteData = await apiRes.json(); // 将数据序列化后存入响应头(需后端配置允许前端读取该头) const response = NextResponse.next(); response.headers.set('X-Site-Data', JSON.stringify(siteData)); return response; } // 配置生效范围(所有路由) export const config = { matcher: '/:path*', };
在页面中读取数据:
服务端可直接从req.headers获取,客户端需通过fetch或响应头读取(需后端配置Access-Control-Expose-Headers: X-Site-Data):
// pages/index.js export async function getServerSideProps(context) { const siteData = JSON.parse(context.req.headers['x-site-data']); return { props: { siteData } }; }
方案三:修复Document.getInitialProps的问题(不推荐)
若坚持使用Document.getInitialProps,需区分服务端/客户端场景获取域名,但该方案仅能处理首次加载,客户端路由跳转时无法更新数据:
export default function Document({ siteData }) { return ( <Html lang="en"> <Head /> <body> {/* 将站点数据注入全局window对象 */} <script dangerouslySetInnerHTML={{ __html: `window.siteData = ${JSON.stringify(siteData)};` }} /> <Main /> <NextScript /> </body> </Html> ); } Document.getInitialProps = async (context) => { let domain; if (context.req) { // 服务端场景 domain = context.req.headers.host.split(':')[0].replace(/^www\./, ''); } else { // 客户端场景(仅首次加载后生效) domain = window.location.hostname.replace(/^www\./, ''); } const apiRes = await fetch( `http://api.com/websitedata?url=${encodeURIComponent(domain)}` ); const siteData = await apiRes.json(); const initialProps = await Document.getInitialProps(context); return { ...initialProps, siteData }; };
注意事项
- 域名处理:需根据实际需求处理
www前缀、端口号等特殊情况 - API缓存:频繁请求API会影响性能,可添加Redis或内存缓存减少重复请求
- 编码安全:使用
encodeURIComponent处理域名,避免特殊字符导致的请求错误
内容的提问来源于stack exchange,提问作者NLAnaconda
相关产品推荐
相关产品推荐

