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

NextJS不使用getInitialProps获取数据库动态菜单填充导航

Next.js 全局导航菜单数据获取实现方案

针对你在Next.js Pages Router架构下,为Layout外层的全局Navigation组件拉取数据库菜单数据、且不使用_app.js中已不推荐的getInitialProps的需求,以下是3种生产可用的实现思路,按推荐优先级排序:


方案1:使用SWR/React Query做客户端缓存拉取(最推荐)

菜单属于全站通用、更新频率极低的公共数据,用轻量数据请求库做客户端拉取+缓存是性价比最高的方案,不会阻塞页面首屏静态生成,页面切换时也不会重复发起请求,还能自由配置缓存策略。
以SWR为例,实现步骤:

  1. 安装依赖:npm install swr
  2. 修改_app.js代码如下:
import "../styles/globals.css";
import Layout from "../components/Layout/Layout";
import Navigation from "../components/Navigation/Navigation";
import FooterPanel from "../components/FooterPanel/FooterPanel";
import { settings } from "../settings/settings";
import DarkModeSwitch from "../components/DarkModeSwitch/DarkModeSwitch";
import useSWR from 'swr'

// 可全局复用的请求方法
const fetcher = (url) => fetch(url).then(res => res.json())

function MyApp({ Component, pageProps, router }) {
  const layoutType = Component.layout || settings.layouts.standard;
  // 拉取菜单数据,配置长缓存+关闭焦点重验证,适配菜单低更新的特性
  const { data: menu, isLoading } = useSWR('/api/menu', fetcher, {
    revalidateOnFocus: false,
    dedupingInterval: 3600000, // 1小时内重复调用直接走缓存
  })

  return (
    <>
      {/* 可传入loading状态给Navigation组件,渲染骨架屏避免位置空白 */}
      <Navigation menu={menu} isLoading={isLoading}/>
      <Layout type={layoutType} router={router}>
        <Component {...pageProps} key={router.asPath} router={router} />
      </Layout>
      <DarkModeSwitch classes="fixed bottom-5 md:bottom-20 right-0 z-10 mr-4" />
      <FooterPanel />
    </>
  );
}

export default MyApp;

如果用React Query,逻辑完全一致,只需要把useSWR替换成useQuery即可。
这个方案的优势:

  • 不需要侵入每个页面的数据获取逻辑,不用给每个页面单独传菜单props
  • 自带请求去重、缓存、错误重试能力,后续菜单更新不需要重新构建整个站点
  • 不会阻塞页面hydration流程,首屏加载速度更快

方案2:页面级数据获取统一注入菜单(适合要求首屏无加载闪烁的场景)

如果你要求首屏SSR/SSG输出的HTML里就已经包含完整菜单数据,不想出现客户端请求的loading态,可以封装一个高阶工具函数,统一给每个页面的getStaticProps/getServerSideProps注入菜单数据,_app.js直接从pageProps中读取即可。
实现步骤:

  1. 封装通用高阶函数:
// utils/withMenuData.js
import { fetchMenuFromDB } from '../lib/db' // 你自己封装的数据库读菜单方法

export function withMenuData(pageDataFunc) {
  return async (context) => {
    // 先执行页面本身的数据获取逻辑
    const pageResult = pageDataFunc ? await pageDataFunc(context) : { props: {} }
    // 拉取菜单数据
    const menu = await fetchMenuFromDB()
    // 合并props返回
    return {
      ...pageResult,
      props: {
        ...pageResult.props,
        menu
      }
    }
  }
}
  1. 每个页面的数据获取方法用这个函数包裹即可,比如首页:
// pages/index.js
import { withMenuData } from '../utils/withMenuData'

export default function Home() {
  return <div>首页内容</div>
}

export const getServerSideProps = withMenuData(async (context) => {
  // 原页面本身的数据请求逻辑
  return {
    props: {
      pageContent: {}
    }
  }
})
  1. 修改_app.js直接读取props里的menu:
function MyApp({ Component, pageProps, router }) {
  const layoutType = Component.layout || settings.layouts.standard;
  const { menu } = pageProps

  return (
    <>
      <Navigation menu={menu}/>
      <Layout type={layoutType} router={router}>
        <Component {...pageProps} key={router.asPath} router={router} />
      </Layout>
      <DarkModeSwitch classes="fixed bottom-5 md:bottom-20 right-0 z-10 mr-4" />
      <FooterPanel />
    </>
  );
}

这个方案的优势:

  • 首屏没有菜单加载闪烁,SEO更友好
  • 完全不依赖客户端请求,对禁用JS的环境更友好

缺点:

  • 每个页面都要包裹高阶函数,有少量重复代码
  • 如果是纯静态生成的站点,菜单更新后需要重新触发全量构建

方案3:全局状态存储菜单数据

如果你的项目本身已经接入了Zustand、Redux、Jotai这类全局状态管理库,可以在应用首次加载时拉取一次菜单数据存入全局状态,Navigation组件直接从全局状态读取即可,逻辑和方案1类似,只是缓存层换成了你项目里已有的状态库。
实现时只需要在_app.js里加个useEffect,首次进入应用时判断如果状态里没有菜单数据就发起请求,后续页面切换直接读缓存即可,不会重复请求。


补充说明:_app.js中的getInitialProps并没有被完全移除,只是使用后会导致整个应用所有页面都放弃自动静态优化,强制走SSR渲染,性能损耗比较大,非极端场景不推荐使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:36:20