如何在避免数据过期的同时实现请求去重与缓存?
解决Next.js中Axios请求去重且避免 stale 数据的方案
你的核心需求是:在同一次页面渲染周期内(Page组件和generateMetadata共享数据)实现请求去重,同时每次页面访问(包括导航返回)都获取最新服务器数据,替代getServerSideProps的行为。
问题根源
React的cache函数是跨请求持久化的,一旦缓存了数据,后续导航回页面时会直接复用旧数据,无法触发新的请求。我们需要的是「仅当前请求内缓存去重,跨请求重新获取」的机制。
方案1:使用Next.js官方noStore(推荐)
通过noStore标记页面为动态渲染,强制每次访问都重新执行数据获取逻辑,同时保留同请求内cache的去重能力。
修改后的代码:
import * as UsersApi from "@/network/api/users"; import { notFound, noStore } from "next/navigation"; // 导入noStore import { NotFoundError } from "@/network/http-errors"; import UserProfilePage from "./UserProfilePage"; import { Metadata } from "next"; import { cache } from "react"; interface PageProps { params: { username: string; } }; const getUser = cache(UsersApi.getUserByUsername); export async function generateMetadata({ params: { username } }: PageProps): Promise<Metadata> { noStore(); // 禁用跨请求缓存,每次请求重新执行 try { const user = await getUser(username); return { title: `${user.username} - Flow Blog`, }; } catch (error) { if (error instanceof NotFoundError) { notFound(); } else { throw error; } } } export default async function Page({ params: { username } }: PageProps) { noStore(); // 禁用跨请求缓存,每次请求重新执行 const user = await getUser(username); return <UserProfilePage user={user} />; }
效果说明
- 每次页面访问(包括从其他页面导航返回)都会触发新的Axios请求,获取最新数据
- 同一次页面渲染中,
generateMetadata和Page组件调用getUser时,会复用同一份请求结果,实现去重
方案2:自定义请求级缓存(精细控制场景)
如果不想让整个页面变为动态,或者需要更精细的缓存控制,可以基于Next.js的请求上下文实现仅当前请求有效的缓存。
实现代码:
import * as UsersApi from "@/network/api/users"; import { notFound } from "next/navigation"; import { NotFoundError } from "@/network/http-errors"; import UserProfilePage from "./UserProfilePage"; import { Metadata } from "next"; import { requestAsyncStorage } from 'next/dist/client/components/request-async-storage'; interface PageProps { params: { username: string; } }; // 创建仅当前请求有效的缓存函数 const createRequestScopedCache = <T extends (...args: any[]) => Promise<any>>(fn: T) => { return async (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>> => { const requestStore = requestAsyncStorage.getStore(); if (!requestStore) return fn(...args); // 用请求上下文存储缓存,每个请求独立一份 requestStore.cache = requestStore.cache || new Map(); const cacheKey = JSON.stringify(args); if (requestStore.cache.has(cacheKey)) { return requestStore.cache.get(cacheKey); } const result = await fn(...args); requestStore.cache.set(cacheKey, result); return result; }; }; // 使用自定义缓存替代React的cache const getUser = createRequestScopedCache(UsersApi.getUserByUsername); export async function generateMetadata({ params: { username } }: PageProps): Promise<Metadata> { try { const user = await getUser(username); return { title: `${user.username} - Flow Blog`, }; } catch (error) { if (error instanceof NotFoundError) { notFound(); } else { throw error; } } } export default async function Page({ params: { username } }: PageProps) { const user = await getUser(username); return <UserProfilePage user={user} />; }
效果说明
- 缓存仅存在于当前请求的上下文内,新请求(包括导航返回)会生成新的缓存容器,确保每次获取最新数据
- 同一次请求内的多次
getUser调用会复用结果,实现去重
内容的提问来源于stack exchange,提问作者Florian Walther
相关产品推荐
相关产品推荐

