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

Next.js SSR中getServerSideProps如何缓存数据存入状态避免重复请求

Next.js SSR模式下全局公共数据免重复拉取实现方案

核心逻辑是做双层缓存:第一层在服务端做内存缓存,砍掉服务端到下游REST API的重复请求开销;第二层在客户端做全局状态缓存,避免页面跳转时重复处理公共数据带来的渲染消耗。


适配Pages Router(使用getServerSideProps场景)

1. 封装服务端公共数据拉取方法,加进程级内存缓存

这一步先解决服务端每次执行getServerSideProps都要请求REST API的问题,针对导航配置这类不随用户身份变化、更新频率低的公共数据,直接在Node服务进程内存中缓存,设置合理过期时间即可。

// lib/server/publicDataCache.js
// 内存缓存变量,服务端进程全局共享
let cachedData = null
let expireTimestamp = 0
// 缓存有效期,根据业务调整,导航类配置可设5-10分钟
const CACHE_TTL = 10 * 60 * 1000

export async function getPublicData() {
  const currentTime = Date.now()
  // 缓存未过期直接返回内存数据,不发API请求
  if (cachedData && currentTime < expireTimestamp) {
    return cachedData
  }
  // 缓存失效才请求下游REST API
  const resp = await fetch('你的公共数据接口地址')
  const data = await resp.json()
  cachedData = data
  expireTimestamp = currentTime + CACHE_TTL
  return data
}

注意:如果公共数据和用户身份绑定(比如带权限的导航配置),不要直接用单例缓存,需要按用户ID拆分缓存key,避免不同用户数据串读。

2. 封装高阶函数统一给所有页面注入公共数据

不用每个页面都重复写公共数据拉取逻辑,包装一个通用的getServerSideProps高阶方法,自动给页面props注入公共数据和拉取时间戳:

// lib/withPublicData.js
import { getPublicData } from './server/publicDataCache'

/**
 * 包装页面getServerSideProps,自动注入公共数据
 * @param {Function} pageGssp 页面原有的getServerSideProps方法
 */
export function withPublicData(pageGssp) {
  return async (context) => {
    // 先执行页面自身的数据拉取逻辑
    const pageResult = pageGssp ? await pageGssp(context) : { props: {} }
    // 拉取公共数据(优先走服务端缓存)
    const publicData = await getPublicData()
    return {
      ...pageResult,
      props: {
        ...pageResult.props,
        publicData,
        publicDataFetchedAt: Date.now()
      }
    }
  }
}

所有需要公共数据的页面直接用这个方法包装即可:

// pages/xxx/index.js
import { withPublicData } from '../../lib/withPublicData'

export const getServerSideProps = withPublicData(async (context) => {
  // 只写页面自身的业务数据拉取逻辑即可
  return {
    props: {
      pageContent: '页面自有数据'
    }
  }
})

3. 客户端全局状态缓存公共数据,避免跳转重复渲染

用React Context(也可以替换为Redux、Zustand等你常用的状态管理工具)在_app.js中存公共数据,只有当新返回的公共数据版本比本地存的新时才更新状态,客户端路由跳转时直接复用已有数据:

// context/PublicDataContext.js
import { createContext, useContext, useState, useMemo } from 'react'

const PublicDataContext = createContext(null)

export function PublicDataProvider({ children, initialData }) {
  // 初始化用SSR注水下来的首屏公共数据
  const [publicData, setPublicData] = useState(initialData)
  const value = useMemo(() => ({ publicData, setPublicData }), [publicData])
  return <PublicDataContext.Provider value={value}>{children}</PublicDataContext.Provider>
}

// 业务组件中直接调用这个hook拿公共数据即可
export function usePublicData() {
  return useContext(PublicDataContext)
}

在_app.js中接入Provider:

// pages/_app.js
import { PublicDataProvider, usePublicData } from '../context/PublicDataContext'
import { useEffect } from 'react'

function AppContent({ Component, pageProps }) {
  const { publicData, setPublicData } = usePublicData()
  useEffect(() => {
    // 只有当新的公共数据拉取时间晚于本地缓存时间,才更新全局状态
    if (
      pageProps.publicDataFetchedAt &&
      (!publicData?.fetchedAt || pageProps.publicDataFetchedAt > publicData.fetchedAt)
    ) {
      setPublicData({
        ...pageProps.publicData,
        fetchedAt: pageProps.publicDataFetchedAt
      })
    }
  }, [pageProps])
  return <Component {...pageProps} />
}

export default function MyApp({ Component, pageProps }) {
  const initialPublicData = pageProps.publicData ? {
    ...pageProps.publicData,
    fetchedAt: pageProps.publicDataFetchedAt
  } : null
  return (
    <PublicDataProvider initialData={initialPublicData}>
      <AppContent Component={Component} pageProps={pageProps} />
    </PublicDataProvider>
  )
}

这套逻辑跑通后,客户端路由跳转时:

  • 服务端执行getServerSideProps时会直接读内存缓存,不会向下游REST API发请求,接口开销接近0
  • 客户端拿到props后发现全局状态已经存在最新的公共数据,不会触发重复更新和渲染,用户侧完全无重复加载感知

如果要进一步优化体验,可以在首次加载后把公共数据存入sessionStorage,用户刷新页面时先读本地缓存快速渲染,后台静默拉取最新版本校验更新即可。


适配Next.js 13+ App Router场景

App Router下实现更简单,不需要自己封装内存缓存:

  • 服务端直接用Next.js官方提供的unstable_cache包装公共数据拉取逻辑,自动做服务端级缓存,支持设置重校验周期:
// lib/getPublicData.js
import { unstable_cache } from 'next/cache'

export const getPublicData = unstable_cache(
  async () => {
    const resp = await fetch('公共数据接口地址')
    return resp.json()
  },
  ['global-public-data'], // 缓存唯一key
  { revalidate: 600 } // 每10分钟重校验一次
)
  • 在根layout.tsx中拉取公共数据,通过React Context向所有子组件传递,App Router的客户端导航会复用根layout的状态,公共数据只会在用户首次进入站点时拉取一次,后续路由跳转完全不会重复请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:48:15