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

Next.js从API获取布局与动态菜单的最优方案及问题排查

问题分析

你的代码存在几个关键问题导致headerData始终为undefined:

  1. 参数名不匹配:_app.tsx中给Layout传递的是siteData,但Layout组件定义接收的是headerData,属性名不一致导致无法接收数据。
  2. 全局变量不可靠:使用全局变量siteHeaderData存储数据,在Next.js的服务器渲染/静态生成环境中,全局变量会被所有请求共享,容易导致数据污染,且客户端渲染时无法保留服务器端赋值的数据。
  3. getInitialProps返回结构错误:你将siteHeaderData嵌套在props对象中返回,但Next.js的getInitialProps返回的所有属性都会直接合并到pageProps中,导致实际pageProps.siteHeaderData无法正确获取。
  4. useEffect的时机问题:useEffect是在组件渲染完成后执行的,第一次渲染Layout时siteHeaderData还是初始的undefined,无法传递有效数据。
解决方案

针对你的核心需求(仅获取一次页头数据并缓存),推荐以下两种适配Next.js Pages Router的方案:

方案1:修正getInitialProps实现(服务器端获取+缓存)

适合需要在服务器端预渲染全局数据的场景,结合Next.js的revalidate实现缓存。

修正后的_app.tsx

import type { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { ContentfulLivePreviewProvider } from '@contentful/live-preview/react';
import { Layout } from '../components/Layout'; // 根据你的实际路径调整

const App = ({ Component, pageProps }: AppProps & { siteHeaderData?: any }) => {
  const { locale } = useRouter();

  return (
    <ContentfulLivePreviewProvider
      enableInspectorMode={pageProps.previewActive}
      enableLiveUpdates={pageProps.previewActive}
      locale={locale || 'en-US'}
    >
      <main>
        {/* 修正属性名,与Layout的接收参数一致 */}
        <Layout headerData={pageProps.siteHeaderData}>
          <Component {...pageProps} />
        </Layout>
      </main>
    </ContentfulLivePreviewProvider>
  );
};

App.getInitialProps = async ({ locale }: any) => {
  const revalidateDuration = 86400; // 缓存1天,可按需调整

  try {
    const pageData = await client.getSite(locale, false);
    const site = pageData.siteCollection?.items[0];

    if (!site) {
      return { notFound: true, revalidate: revalidateDuration };
    }

    // 直接返回siteHeaderData,无需嵌套在props中
    return {
      siteHeaderData: site,
      revalidate: revalidateDuration,
    };
  } catch (error) {
    console.error('Failed to fetch site header data:', error);
    return {
      notFound: true,
      revalidate: revalidateDuration,
    };
  }
};

export default App;

修正后的Layout.tsx

import { ReactNode } from 'react';
import { Header } from './Header';
import { Footer } from './Footer';

interface LayoutPropsInterface {
  children: ReactNode;
  headerData?: any;
}

export const Layout = ({ headerData, children }: LayoutPropsInterface) => {
  // 现在可以正常访问headerData了
  console.log('Layout headerData:', headerData);
  
  return (
    <>
      <div className='main-wrapper wrapper-inner'>
        {/* 将headerData传递给Header组件使用 */}
        <Header data={headerData} />
        {children}
        <Footer data={headerData?.footer} />
      </div>
    </>
  );
};

方案2:使用SWR实现客户端缓存(推荐)

适合需要在客户端缓存数据、避免重复请求的场景,SWR会自动处理缓存、重新验证和错误重试。

步骤1:安装SWR

npm install swr
# 或
yarn add swr

步骤2:创建数据获取钩子

在hooks/useSiteData.ts中:

import useSWR from 'swr';
import client from '../path-to-your-graphql-client'; // 导入你的Contentful GraphQL客户端

export const useSiteData = (locale: string) => {
  const fetcher = async () => {
    const pageData = await client.getSite(locale, false);
    return pageData.siteCollection?.items[0];
  };

  // 使用locale作为缓存键的一部分,支持多语言场景
  const { data, error } = useSWR(`site-data-${locale}`, fetcher, {
    revalidateOnFocus: false, // 失去焦点后不自动重新验证
    revalidateIfStale: false, // 数据过期前不重新验证
    dedupingInterval: 86400000, // 1天内重复请求合并为一次
  });

  return {
    siteData: data,
    isLoading: !error && !data,
    isError: error,
  };
};

步骤3:在_app.tsx中使用钩子

import type { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { ContentfulLivePreviewProvider } from '@contentful/live-preview/react';
import { Layout } from '../components/Layout';
import { useSiteData } from '../hooks/useSiteData';

const App = ({ Component, pageProps }: AppProps) => {
  const { locale } = useRouter();
  const { siteData, isLoading, isError } = useSiteData(locale || 'en-US');

  // 处理加载中或错误状态
  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Failed to load site data</div>;

  return (
    <ContentfulLivePreviewProvider
      enableInspectorMode={pageProps.previewActive}
      enableLiveUpdates={pageProps.previewActive}
      locale={locale || 'en-US'}
    >
      <main>
        <Layout headerData={siteData}>
          <Component {...pageProps} />
        </Layout>
      </main>
    </ContentfulLivePreviewProvider>
  );
};

// 移除getInitialProps,改用SWR处理数据获取和缓存
export default App;
关键说明
  • 缓存策略:方案1中revalidate参数控制Next.js的增量静态再生(ISR),在服务器端缓存数据指定时长;方案2中SWR的dedupingInterval控制客户端缓存时长,避免重复请求。
  • 多语言支持:两种方案都考虑了locale参数,确保不同语言的页头数据独立获取和缓存。
  • 避免全局变量:全局变量在Next.js中存在数据泄漏风险,始终通过组件props或状态管理工具传递全局数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:34:53