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
相关产品推荐
相关产品推荐

