Description
Describe the bug
I've been struggling to get the Suspense prefetch-with-streaming pattern working in my app when there are multiple prefetch queries (because one prefetch request depends on the result of the other).
The problem is that the queryFn
gets called for the second useSuspenseQuery
call during SSR. For example:
// function to generate options for the query to get a given Pokemon
export const getPokemonOptions = (id: number) =>
queryOptions({
queryKey: ["pokemon", id],
queryFn: async () => {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
return response.json();
},
});
// code in the RSC to perform two serial fetch operations
void queryClient
.fetchQuery({
queryKey: ["favoritePokemonId"],
queryFn: () =>
// simulate database call to look up the user's favorite Pokemon
new Promise<number>((resolve) =>
setTimeout(() => resolve(Math.ceil(Math.random() * 100)), 500)
),
})
.then((favoritePokemonId) => {
return queryClient.prefetchQuery(getPokemonOptions(favoritePokemonId));
});
// code in the Suspense-wrapped client component to hydrate the data
const { data: favoritePokemonId } = useSuspenseQuery({
queryKey: ["favoritePokemonId"],
queryFn: (): number => {
throw new Error("First queryFn in pokemon-info should never be called.");
},
});
const { data: pokemon } = useSuspenseQuery({
...getPokemonOptions(favoritePokemonId),
queryFn: () => {
throw new Error("Second queryFn in pokemon-info should never be called");
},
});
This code causes SSR to fail with the error Second queryFn in pokemon-info should never be called
.
Interestingly, if you stub out the queryFn
with a no-op function, the code seems to work, with the second useSuspenseQuery
returning data immediately. That suggests that the Suspense
boundary is correctly blocking the component with the useSuspenseQuery
from rendering until the prefetch has completed, but then it's incorrectly trying to fetch the prefetched data again, even with staleTime: Infinity
.
Your minimal, reproducible example
https://codesandbox.io/p/devbox/dreamy-sky-znq6jh?workspaceId=ws_CZBXqWgnzWZNdzjDui2die
Steps to reproduce
- Prefetch data serially without
await
to allow for streaming - Use
useSuspenseQuery
for all prefetched data from a component inside of a<Suspense>
boundary
Expected behavior
The clientFn
provided to useSuspenseQuery
should never be called during SSR, because the hook is for loading prefetched data. (It should only be called on the client after the data is invalidated.)
How often does this bug happen?
Every time
Platform
macOS, but reproducible in CodeSandbox. Reproducible under Next.js 15 w/ React 19, and Next.js 14 w/ React 18.3.
TanStack Query version
5.68.0