T
TanStack2w ago
multiple-amethyst

NextJS: Prefetching/Hydration overwriting fresher cache data when using streaming SSR with staleTime

Hey folks, tought i might also ask here on discord, here is the original discussion link: https://github.com/TanStack/query/discussions/9559 I’ve run into something odd and couldn’t find an existing discussion about it (maybe it’s a bug, maybe I’m missing something 🤔). Setup: - Next.js 15+ - TanStack Query 5+ - Prefetching + streaming hydration as described here - staleTime on the server side = 5 seconds (but issue happens with longer/shorter times too) Scenario: 1. On the server, I prefetch /users with a staleTime of 5s. 2. On the client, I load the same query via useQuery and render a table of users. Now, within that staleTime window the following needs to happen to experience my issue: 3. A user deletes a row → I send the request, optimistically update the UI with useMutation. TanStack Query Devtools show the cache updated correctly, with “Last updated” set to now. 4. The user refreshes the page → since we’re still inside staleTime, the server doesn’t refetch, but hydrates old data from the initial prefetch, overwriting the newer client cache. Devtools now show the “Last updated” time as the timestamp from the original page load. Result: the deleted row reappears, even though it’s actually gone on the server. What I’d expect: When hydrating, the server-side data should be skipped if the client already has newer/fresher data in the cache. Question: Is this expected behavior? Am I missing a setting here? Or could this be a Next.js SSR quirk?
GitHub
NextJS: Prefetching/Hydration overwriting fresher cache data when u...
Hey folks, I’ve run into something odd and couldn’t find an existing discussion about it (maybe it’s a bug, maybe I’m missing something 🤔). Setup: Next.js 15+ TanStack Query 5+ Prefetching + stream...
14 Replies
like-gold
like-gold2w ago
since we’re still inside staleTime, the server doesn’t refetch
if you refresh the page, the server will run. there is no caching on the server in react query you'd probably need to customize nextJs's caching to not re-run the server (I think they also have a staleTime setting) or do some caching of the query-client on the server
multiple-amethyst
multiple-amethystOP2w ago
I did the following: 1. Setting expirimental "staleTime" (next.config.mjs) for dynamic and static to "0" 2. While also calling "router.refresh" after the deletion of the row. The error still persists, tanstack query still hydrates the old data received from the server (im not sure why it receives that data and what kinda cache is doing this since i now basicly disabled the nextjs cache and forcefully refresh it.) I can also confirm that the page reload itself is happening (i forcefully refresh in my browser) and a console.log on the backend shows that i am reaching the new queryClient.prefetchQuery call. However queryFn for whatever strange reason is not called (i console.log inside ther), it is only called after the 5 second staleTime (of the backend prefetch request) are over. (With no cache on the backend, why has it a staleTime setting, and how does it know when it is reached?). It btw. is not my frontend re-fetching it after the 5 seconds because my frontend query has a staleTime of Number.Infinite
multiple-amethyst
multiple-amethystOP2w ago
I made a small repo which contains code to reproduce it: https://github.com/toitzi/react-query-cache-issue
GitHub
GitHub - toitzi/react-query-cache-issue: Repo to reproduce my cache...
Repo to reproduce my cache issue (Stale data re-appears) - toitzi/react-query-cache-issue
like-gold
like-gold2w ago
you create a const queryClient = new QueryClient() outside of your component, on the server, which means it just lives there and is shared between all requests. The docs explicitly mention to not do that https://github.com/toitzi/react-query-cache-issue/blob/1cdeab6592e3add6cee69d96be6d1ffc6e72c8e5/app/page.js#L6 that's why we have this makeQueryClient() function that always creates a new client when on the server
like-gold
like-gold2w ago
https://tanstack.com/query/v5/docs/framework/react/guides/ssr#initial-setup
// NEVER DO THIS:
// const queryClient = new QueryClient()
//
// Creating the queryClient at the file root level makes the cache shared
// between all requests and means _all_ data gets passed to _all_ users.
// Besides being bad for performance, this also leaks any sensitive data.
// NEVER DO THIS:
// const queryClient = new QueryClient()
//
// Creating the queryClient at the file root level makes the cache shared
// between all requests and means _all_ data gets passed to _all_ users.
// Besides being bad for performance, this also leaks any sensitive data.
Server Rendering & Hydration | TanStack Query React Docs
In this guide you'll learn how to use React Query with server rendering. See the guide on for some background. You might also want to check out the before that. For advanced server rendering patterns,...
multiple-amethyst
multiple-amethystOP2w ago
This page is server side you sent, and not my provider, in my provider i have: it correctly and according to the docs: https://github.com/toitzi/react-query-cache-issue/blob/1cdeab6592e3add6cee69d96be6d1ffc6e72c8e5/lib/providers/query-client-provider.jsx The snippet you shared it from the server page, looking at the documentation for ssr: https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr this is the correct way to do it on the server side page
GitHub
react-query-cache-issue/lib/providers/query-client-provider.jsx at ...
Repo to reproduce my cache issue (Stale data re-appears) - toitzi/react-query-cache-issue
Advanced Server Rendering | TanStack Query React Docs
Welcome to the Advanced Server Rendering guide, where you will learn all about using React Query with streaming, Server Components and the Next.js app router. You might want to read the before this on...
multiple-amethyst
multiple-amethystOP2w ago
The difference is in the docs it is done inside the component and i have it outside putting it inside the server component, makes seemingly the prefetch not work at all (i updated my repo, if you move the query client call inside the server component, the frontend executes/waits for the frontend request instead of hydrating the prefetched data, i added an artificial delay so this is noticable)
like-gold
like-gold2w ago
every place in the SSR docs creates a new QueryClient() inside the server component before calling prefetchQuery: - https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data just search for prefetchQuery, it's always:
export default async function PostsPage() {
const queryClient = new QueryClient()

await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: getPosts,
})
export default async function PostsPage() {
const queryClient = new QueryClient()

await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: getPosts,
})
Advanced Server Rendering | TanStack Query React Docs
Welcome to the Advanced Server Rendering guide, where you will learn all about using React Query with streaming, Server Components and the Next.js app router. You might want to read the before this on...
like-gold
like-gold2w ago
whenever you create the QueryClient in the module scope, it will live on after a request on the server. Which is why we document that this shouldn't be done. so yes, the idea is: there is no caching on the server between requests. When you call prefetchQuery, the fetch will happen. always. staleTime on prefetchQuery doesn't help. it's like doing const data = await fetch(...) in a server component. This will always happen when the server component "runs". The idea is to not make the server component run, with next's staleTime or static or prerendering or whatever else they offer to not run server components
multiple-amethyst
multiple-amethystOP2w ago
okay thank you very much i understand that part now, but if i move it inside the component like you shared, data is not prefetched / hydrated instantly? It will still use the fetch from the frontend and wait for that data?
like-gold
like-gold2w ago
no that should work: - you prefetch on the server - the HydrationBoundary sends it to the client - the client picks it up and puts it into the QueryCache on the client - if you have staleTime on the client, no fetch from the client should happen
multiple-amethyst
multiple-amethystOP2w ago
1. Yes that is what i expected, i updated my repository, in my prefetch function i load the data instantly (few ms) and dehydrate 2. In my frontend i use an action with an aritificial 2 seconds delay If i load the page now no prefetched data is visible, it will fetch via the action from the frontend and wait those 2 seconds
like-gold
like-gold2w ago
your issue with the current code, is, again, documented here: https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr#streaming-with-server-components you prefetch without await,which means we stream the promise to the client. In that case you need useSuspensQuery, not useQuery.
Note that you could also useQuery instead of useSuspenseQuery, and the Promise would still be picked up correctly. However, NextJs won't suspend in that case and the component will render in the pending status, which also opts out of server rendering the content.
so you either need to await prefetchQuery(..) or switch to using suspense
Advanced Server Rendering | TanStack Query React Docs
Welcome to the Advanced Server Rendering guide, where you will learn all about using React Query with streaming, Server Components and the Next.js app router. You might want to read the before this on...
multiple-amethyst
multiple-amethystOP2w ago
strangly enough, using suspense query and a suspense boundary will still wait to display content until the client query is done, it does not show me the prefetched hydrated state. I can use the expirmental ReactQueryStreamedHydration and omit the prefetch, the queryFn then seems to correctly start its run on the server and work as i would expect, i think thats okay for my case but do you have a guess why the first case is not working? Thanks a lot btw for your help (it works in the first case if i await the prefetch, but that should not be necessary afaik) ah nevermind i figured my problem, thank you very much for all the help! 🙂

Did you find this page helpful?