Streaming Support for RR Framework mode #10318
-
|
Hey there i just wanted to ask if there is something to use Streaming with Tanstack Query and React Router Framework Mode (aka Remix). |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
|
Yep, streaming is supported in TanStack Query v5 with React Router Framework Mode. The key is combining RR's The pattern In your loader, await critical data (blocks until ready) and kick off non-critical prefetches without awaiting (these stream down as they resolve): // app/routes/post.$id.tsx
import { defer } from 'react-router';
import { queryClient } from '~/lib/query-client';
export async function loader({ params }: Route.LoaderArgs) {
// Critical data — await it, blocks the loader
const post = await queryClient.fetchQuery({
queryKey: ['post', params.id],
queryFn: () => fetchPost(params.id),
});
// Non-critical — don't await, will stream
const commentsPromise = queryClient.prefetchQuery({
queryKey: ['comments', params.id],
queryFn: () => fetchComments(params.id),
});
return defer({ post, commentsPromise });
}On the client, wrap the deferred part in import { Await, useLoaderData } from 'react-router';
import { useSuspenseQuery } from '@tanstack/react-query';
export default function PostPage() {
const { post, commentsPromise } = useLoaderData<typeof loader>();
return (
<div>
<h1>{post.title}</h1>
<Suspense fallback={<p>Loading comments...</p>}>
<Await resolve={commentsPromise}>
<Comments postId={post.id} />
</Await>
</Suspense>
</div>
);
}
function Comments({ postId }: { postId: string }) {
// By the time this renders, the prefetch is done (or in-flight)
const { data } = useSuspenseQuery({
queryKey: ['comments', postId],
queryFn: () => fetchComments(postId),
});
return <ul>{data.map(c => <li key={c.id}>{c.body}</li>)}</ul>;
}A couple of things worth knowing:
// app/entry.server.tsx
export const streamTimeout = 10_000; // msReact Router v7 shortcut In RR v7 you can skip export async function loader({ params }: Route.LoaderArgs) {
const post = await queryClient.fetchQuery(postQuery(params.id));
return {
post,
commentsPromise: queryClient.prefetchQuery(commentsQuery(params.id)),
};
}Optional: If you want queries to start fetching during render rather than waiting for effects, enable this in your const queryClient = new QueryClient({
defaultOptions: {
queries: { experimental_prefetchInRender: true },
},
});This is experimental and opt-in, but it can improve perceived performance in SSR scenarios. The TanStack Query Advanced Server Rendering guide and the React Router Suspense guide both cover this in detail. There's also a working React Router example in the TanStack repo. |
Beta Was this translation helpful? Give feedback.
-
|
You only need streaming if you want the server to start the non-critical work before the client hydrates, while still sending the initial shell early. If you just render server sends shell -> browser hydrates -> query starts in browserWith React Router streaming, the sequence can be: server starts query -> server sends shell/fallback -> deferred value resolves -> client receives streamed valueSo streaming is mostly useful when you want to avoid waiting for all data before sending HTML, but you also do not want every non-critical request to start only after hydration. About this part: <Await resolve={commentsPromise}>
{(comments) => <HydrationBoundary state={comments}>...</HydrationBoundary>}
</Await>
If you want the streamed value to seed Query, the rough idea is: const comments = await queryClient.fetchQuery(commentsQuery(postId))
return dehydrate(queryClient)and then inside TanStack Router/Start has tighter integration for this kind of server/query streaming. React Router framework mode gives you the lower-level streaming primitive ( |
Beta Was this translation helpful? Give feedback.
You only need streaming if you want the server to start the non-critical work before the client hydrates, while still sending the initial shell early.
If you just render
useSuspenseQueryon the client, that also works, but the sequence is different:With React Router streaming, the sequence can be:
So streaming is mostly useful when you want to avoid waiting for all data before sending HTML, but you also do not want every non-critical request to start only after hydration.
About this part: