Next.js从API获取布局与动态菜单的最优方案及问题排查
问题分析
你的代码存在几个关键问题导致headerData始终为undefined:
- 参数名不匹配:_app.tsx中给Layout传递的是
siteData,但Layout组件定义接收的是headerData,属性名不一致导致无法接收数据。 - 全局变量不可靠:使用全局变量
siteHeaderData存储数据,在Next.js的服务器渲染/静态生成环境中,全局变量会被所有请求共享,容易导致数据污染,且客户端渲染时无法保留服务器端赋值的数据。 - getInitialProps返回结构错误:你将
siteHeaderData嵌套在props对象中返回,但Next.js的getInitialProps返回的所有属性都会直接合并到pageProps中,导致实际pageProps.siteHeaderData无法正确获取。 - 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
相关产品推荐
相关产品推荐

