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

Next.js集成React-query实现SSR/SSG时水合配置无法复用缓存,每次需重新请求的问题排查

Fixing React Query SSR/SSG Cache Reuse in Next.js

Let's break down why your cache isn't working as expected and walk through the correct configuration steps:

Core Issues in Your Current Setup

First, let's clarify the main problems you're facing:

  1. Development Mode Behavior: In Next.js dev mode, getStaticProps runs on every page request (instead of only at build time), so you'll see the 3-second delay on every refresh. This is normal for dev mode—production builds will eliminate this.
  2. Incorrect Cache Check Logic: Using a global QueryClient on the server is a bad practice (it causes cache pollution across concurrent requests) and isn't needed.
  3. Potential Hydration Misconfiguration: If client-side navigation still triggers loads, your hydration step might not be correctly injecting the server-side cache into the client's QueryClient.

Correct Configuration Steps

1. Fix the App Component's QueryClient Setup

Use useMemo instead of useState to ensure a single client instance is created once on the client:

import { useMemo } from 'react'
import { QueryClient, QueryClientProvider, Hydrate } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { ThemeProvider, CssBaseline } from '@mui/material'
import type { AppProps } from 'next/app'

const App = ({ Component, pageProps }: AppProps) => {
  // Create a single QueryClient instance for the client
  const queryClient = useMemo(
    () => new QueryClient({
      defaultOptions: {
        queries: {
          staleTime: 30 * 1000, // 30 seconds before data is considered stale
          cacheTime: 5 * 60 * 1000, // Keep cached data for 5 minutes (default)
        }
      }
    }),
    []
  )

  return (
    <QueryClientProvider client={queryClient}>
      {/* Hydrate must be inside QueryClientProvider to access the client */}
      <Hydrate state={pageProps.dehydratedState}>
        <ReactQueryDevtools initialIsOpen={false} />
        <ThemeProvider theme={theme}>
          <CssBaseline />
          <Component {...pageProps} />
        </ThemeProvider>
      </Hydrate>
    </QueryClientProvider>
  )
}

export default App

2. Proper Server-Side Prefetching (getStaticProps/getServerSideProps)

Always create a new QueryClient per server request—this avoids concurrent request cache pollution:

import { QueryClient, dehydrate, prefetchQuery } from '@tanstack/react-query'
import type { GetStaticProps, NextPage } from 'next'
import { fetchTodos, Todo } from '../utils/api'

const Home: NextPage = () => {
  const { data, isLoading, isFetching } = useQuery<Todo[]>('todos', fetchTodos)

  // Differentiate between initial load (no cache) and background refresh
  if (isLoading) return <h1>Loading...</h1>
  if (isFetching) return <div className="text-sm">Refreshing data...</div>

  return (
    <div className="container mx-auto p-4">
      <h1>Todos</h1>
      <ul className="space-y-2">
        {data?.map(todo => (
          <li key={todo.id} className="p-2 border rounded">
            {todo.title}
          </li>
        ))}
      </ul>
    </div>
  )
}

export const getStaticProps: GetStaticProps = async () => {
  // Create a fresh QueryClient for this server request
  const queryClient = new QueryClient()
  await queryClient.prefetchQuery<Todo[]>('todos', fetchTodos)

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
    // Optional: Enable Incremental Static Regeneration to refresh data in production
    revalidate: 30,
  }
}

export default Home

3. Key Fixes & Explanations

  • Server-Side Client Isolation: Creating a new QueryClient per server request is the recommended approach (per React Query docs) to prevent cross-request data leaks.
  • Hydration Validation: Ensure pageProps.dehydratedState is not undefined—log it in your App component to confirm it's being passed correctly.
  • Loading State Differentiation: Use isLoading only for initial loads (when no cache exists) and isFetching for background refreshes. This avoids showing a full loading screen when the cache is being refreshed in the background.
  • Production vs Dev Mode: In production, getStaticProps runs only at build time (or on revalidation), so you won't see the 3-second delay on refreshes. Dev mode's behavior is intentional to let you test fresh data.

Why Your Global QueryClient Solution Is Risky

Using a global QueryClient on the server can lead to:

  • Cache Pollution: Concurrent requests will overwrite each other's cached data, leading to incorrect data being served to users.
  • Memory Leaks: The global client will accumulate cached data indefinitely, increasing server memory usage.
  • Hot Reload Issues: In dev mode, hot reloads will reset the global client, causing unexpected cache behavior.

内容的提问来源于stack exchange,提问作者Bernardo Lopes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 19:27:42