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

Next.js 14.2.3(页面路由)getServerSideProps+React Query缓存失效问题

问题分析与解决方案

核心问题根源

你遇到的重复请求问题,本质是每次getServerSideProps执行时都新建了QueryClient实例。React Query的缓存是绑定在单个QueryClient实例上的,每次页面路由切换触发getServerSideProps时,新的QueryClient完全没有之前的缓存数据,自然会重复发起API请求,哪怕你设置了staleTime和gcTime也无效。

解决方案:服务端共享缓存

要实现服务端缓存,需要让不同请求的QueryClient复用同一缓存源,有两种可行方案:

方案1:使用全局QueryClient实例(单实例部署场景)

在服务端创建一个全局的QueryClient,所有getServerSideProps请求复用该实例,利用其内置缓存避免重复请求。

// 仅在服务端运行的全局QueryClient实例
let serverQueryClient = null;

function getServerQueryClient() {
  if (!serverQueryClient) {
    serverQueryClient = new QueryClient({
      defaultOptions: {
        queries: {
          staleTime: 5 * 60 * 1000, // 5分钟新鲜期
          gcTime: 5 * 60 * 1000,    // 5分钟缓存回收时间
          cacheTime: 5 * 60 * 1000,
        },
      },
    });
  }
  return serverQueryClient;
}

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { provider_name } = context.params as Params;
  const queryClient = getServerQueryClient(); // 复用全局实例

  // 先检查缓存,不存在才发起请求
  let providerInfo = queryClient.getQueryData(providerInfoQueryKey(provider_name));
  if (!providerInfo) {
    providerInfo = await queryClient.fetchQuery({
      queryKey: providerInfoQueryKey(provider_name),
      queryFn: () => apiGetProviderInfo(provider_name),
    });
  }

  const providerId = providerInfo.data.id;

  const prefetches = [];
  const queryConfigs = [
    { queryKey: providerNoteQueryKey({ providerId }), queryFn: () => apiGetProviderNotes(providerId) },
    { queryKey: hotProductsQueryKey({ providerId }), queryFn: () => apiGetHotProducts(providerId) },
    { queryKey: providerWorkingHoursQueryKey({ providerId }), queryFn: () => apiGetWorkingHours(providerId) },
    { queryKey: menuSettingsQueryKey({ providerId }), queryFn: () => apiGetMenuSettings(providerId) },
    { queryKey: providerThemeQueryKey(providerId), queryFn: () => apiGetProviderTheme(providerId) },
    { queryKey: menuListQueryKey({ providerId }), queryFn: () => apiGetMenuList(providerId) },
  ];

  // 批量检查缓存,仅无缓存时发起预取
  for (const config of queryConfigs) {
    if (!queryClient.getQueryData(config.queryKey)) {
      prefetches.push(queryClient.prefetchQuery(config));
    }
  }

  await Promise.all(prefetches);

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
};

方案2:使用外部分布式缓存(多实例部署场景)

如果你的应用是多实例集群部署(如Docker、K8s),全局QueryClient的缓存无法跨实例共享,此时需要用Redis等分布式缓存存储查询结果:

import LRU from 'lru-cache';
// 生产环境替换为Redis客户端
const cache = new LRU({ max: 100, ttl: 5 * 60 * 1000 });

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { provider_name } = context.params as Params;
  const queryClient = new QueryClient();

  // 先查外部缓存
  let providerInfo = cache.get(providerInfoQueryKey(provider_name));
  if (!providerInfo) {
    providerInfo = await apiGetProviderInfo(provider_name);
    cache.set(providerInfoQueryKey(provider_name), providerInfo);
  }
  // 将缓存数据同步到QueryClient用于客户端脱水
  queryClient.setQueryData(providerInfoQueryKey(provider_name), providerInfo);

  const providerId = providerInfo.data.id;
  const queryConfigs = [
    { queryKey: providerNoteQueryKey({ providerId }), queryFn: () => apiGetProviderNotes(providerId) },
    { queryKey: hotProductsQueryKey({ providerId }), queryFn: () => apiGetHotProducts(providerId) },
    { queryKey: providerWorkingHoursQueryKey({ providerId }), queryFn: () => apiGetWorkingHours(providerId) },
    { queryKey: menuSettingsQueryKey({ providerId }), queryFn: () => apiGetMenuSettings(providerId) },
    { queryKey: providerThemeQueryKey(providerId), queryFn: () => apiGetProviderTheme(providerId) },
    { queryKey: menuListQueryKey({ providerId }), queryFn: () => apiGetMenuList(providerId) },
  ];

  // 批量处理缓存与同步
  for (const config of queryConfigs) {
    let data = cache.get(config.queryKey);
    if (!data) {
      data = await config.queryFn();
      cache.set(config.queryKey, data);
    }
    queryClient.setQueryData(config.queryKey, data);
  }

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
};

客户端补充配置

确保_app.tsx中正确处理脱水状态,避免客户端重复请求:

import { QueryClient, QueryClientProvider, Hydrate } from '@tanstack/react-query';
import { AppProps } from 'next/app';

function MyApp({ Component, pageProps }: AppProps) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 5 * 60 * 1000,
        gcTime: 5 * 60 * 1000,
      },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  );
}

export default MyApp;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 20:34:52