使用react-query与Next.js时能否实现服务端数据缓存?
React Query 默认是客户端状态管理库,服务端每次执行页面渲染逻辑(包括getStaticProps)时如果每次新建QueryClient实例,缓存是无法跨页面/跨请求共享的。你之前只通过initialData传递数据到客户端,仅能避免客户端重复请求,无法减少服务端到Firebase的请求次数。
方案1:轻量服务端内存缓存(最易实现)
直接在服务端用内存缓存工具封装Firebase请求逻辑,不管哪个页面的getStaticProps调用,都优先走缓存,命中就不用请求Firebase。
- 第一步:安装轻量缓存库
npm install node-cache - 第二步:封装全局缓存实例和Firebase请求方法
// utils/serverCache.js import NodeCache from 'node-cache'; // 配置缓存有效期,示例为5分钟,可根据项目更新频率调整 const firebaseCache = new NodeCache({ stdTTL: 300 }); export default firebaseCache;
// services/projectService.js import firebaseCache from '../utils/serverCache'; import { db } from '../firebase/config'; export const fetchAllProjects = async () => { const CACHE_KEY = 'projects:all'; // 优先读取缓存 const cachedProjects = firebaseCache.get(CACHE_KEY); if (cachedProjects) return cachedProjects; // 缓存未命中再请求Firebase const projectSnapshot = await db.collection('projects').get(); const projects = projectSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); // 写入缓存 firebaseCache.set(CACHE_KEY, projects); return projects; };
- 第三步:所有页面的
getStaticProps直接调用封装后的方法即可
// pages/projects.jsx 和 pages/projects/[id].jsx 的getStaticProps通用 import { fetchAllProjects } from '../services/projectService'; export async function getStaticProps() { const projects = await fetchAllProjects(); // 其他逻辑处理,比如[id].jsx可以直接从全量projects里过滤对应id的项目 return { props: { projects }, revalidate: 300 // 和缓存有效期保持一致即可 }; }
方案2:React Query 服务端预取+Hydration(兼顾客户端和服务端缓存)
如果想要同时复用React Query的缓存能力,避免客户端重复请求,可以用React Query官方的脱水/注水能力,同时复用服务端的QueryClient实例实现缓存共享。
- 第一步:封装通用QueryClient生成逻辑
// utils/queryClient.js import { QueryClient } from '@tanstack/react-query'; // 服务端全局复用同一个实例,避免每次请求新建导致缓存失效 let serverQueryClient; export const getServerQueryClient = () => { if (!serverQueryClient) { serverQueryClient = new QueryClient({ defaultOptions: { queries: { cacheTime: 1000 * 60 * 5, // 缓存5分钟 }, }, }); } return serverQueryClient; }; // 客户端独立生成实例即可 export const createClientQueryClient = () => new QueryClient();
- 第二步:在
getStaticProps中预取数据
import { dehydrate } from '@tanstack/react-query/hydration'; import { getServerQueryClient } from '../utils/queryClient'; import { fetchAllProjects } from '../services/projectService'; export async function getStaticProps() { const queryClient = getServerQueryClient(); // 预取数据,缓存存在时不会重复执行fetchAllProjects请求Firebase await queryClient.prefetchQuery(['projects'], fetchAllProjects); return { props: { dehydratedState: dehydrate(queryClient), }, revalidate: 300, }; }
- 第三步:在
_app.jsx中完成客户端注水
import { Hydrate, QueryClientProvider } from '@tanstack/react-query'; import { createClientQueryClient } from '../utils/queryClient'; export default function MyApp({ Component, pageProps }) { const queryClient = createClientQueryClient(); return ( <QueryClientProvider client={queryClient}> <Hydrate state={pageProps.dehydratedState}> <Component {...pageProps} /> </Hydrate> </QueryClientProvider> ); }
- 第四步:页面组件中直接用
useQuery即可,不需要再传initialData
const { data: projects } = useQuery(['projects'], fetchAllProjects);
补充说明
如果你的项目部署在Serverless环境,实例冷启动会清空内存缓存,需要跨实例共享缓存的话可以把node-cache替换为Redis即可。
内容的提问来源于stack exchange,提问作者Shiladitya Thakur
相关产品推荐
相关产品推荐

