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:
- Development Mode Behavior: In Next.js dev mode,
getStaticPropsruns 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. - Incorrect Cache Check Logic: Using a global
QueryClienton the server is a bad practice (it causes cache pollution across concurrent requests) and isn't needed. - 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
QueryClientper server request is the recommended approach (per React Query docs) to prevent cross-request data leaks. - Hydration Validation: Ensure
pageProps.dehydratedStateis notundefined—log it in your App component to confirm it's being passed correctly. - Loading State Differentiation: Use
isLoadingonly for initial loads (when no cache exists) andisFetchingfor background refreshes. This avoids showing a full loading screen when the cache is being refreshed in the background. - Production vs Dev Mode: In production,
getStaticPropsruns 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
相关产品推荐
相关产品推荐

