You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS如何在_document.js/_app.js中可靠获取主机名并请求对应API数据

可靠实现NextJS多域名动态API请求方案

针对你遇到的Document.getInitialProps中req不稳定的问题,核心原因是该方法在**客户端渲染场景(如客户端路由跳转)**下不会获取到服务端的req对象,导致无法稳定拿到域名。以下是几种可行的可靠解决方案:

方案一:在_app.js中使用getServerSideProps(Pages Router推荐)

getServerSideProps会在每次请求的服务端阶段执行,无论首次加载还是客户端路由跳转,都能稳定获取到服务端的req对象,从而拿到当前访问域名。

实现步骤:

  1. 在pages/_app.js中定义getServerSideProps,请求API获取站点数据后传递给应用组件
  2. 可选:将站点数据存入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传递给页面。

实现步骤:

  1. 项目根目录创建middleware.js
  2. 在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 };
};

注意事项

  1. 域名处理:需根据实际需求处理www前缀、端口号等特殊情况
  2. API缓存:频繁请求API会影响性能,可添加Redis或内存缓存减少重复请求
  3. 编码安全:使用encodeURIComponent处理域名,避免特殊字符导致的请求错误

内容的提问来源于stack exchange,提问作者NLAnaconda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 07:35:15