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

如何在避免数据过期的同时实现请求去重与缓存?

解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 05:05:24