Capítulo 53 de 57
The @tanstack/react-router-ssr-query package wires TanStack Query into TanStack Router's SSR pipeline, automating QueryClient dehydration/hydration, streaming queries that resolve during the initial server render, and handling redirect() thrown from queries or mutations.
setupRouterSsrQueryIntegration({ router, queryClient }): The core setup call that wires the router and query client together for automatic SSR dehydration/hydration and streaming.QueryClient: In SSR environments, a fresh QueryClient must be created per request (typically inside a getRouter() factory function) to avoid cross-request state leakage.wrapQueryClient: Defaults to wrapping the app with QueryClientProvider automatically; set to false if you already provide your own provider.dehydrateOptions / hydrateOptions: Customize SSR serialization, e.g. shouldDehydrateQuery to exclude certain queries, or hydrateOptions.defaultOptions.queries.gcTime to control cache lifetime post-hydration.useSuspenseQuery vs useQuery: useSuspenseQuery executes on the server during SSR and streams to the client as it resolves; plain useQuery never runs server-side, it only fetches after client hydration.context.queryClient.ensureQueryData(queryOptions) in a route's loader avoids client-side fetch waterfalls; the component then reads the same query via useSuspenseQuery.queryClient.fetchQuery(...) in a loader without awaiting or returning the promise starts the query on the server and streams the result to the client without blocking the SSR response; awaiting/returning it blocks SSR until it resolves.handleRedirects: Enabled by default; intercepts a redirect() thrown from a query/mutation and performs a client-side router navigation. Disable with handleRedirects: false for custom handling.import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'
export function getRouter() {
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
context: { queryClient },
scrollRestoration: true,
defaultPreload: 'intent',
})
setupRouterSsrQueryIntegration({ router, queryClient })
return router
}
const postsQuery = queryOptions({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then((r) => r.json()),
})
export const Route = createFileRoute('/posts')({
loader: ({ context }) => context.queryClient.ensureQueryData(postsQuery),
component: PostsPage,
})
function PostsPage() {
const { data } = useSuspenseQuery(postsQuery)
return <div>{data.map((p: any) => p.title).join(', ')}</div>
}
loader and reading it via useSuspenseQuery for SSR + streaming without a loading flash.QueryClient per SSR request via a getRouter() factory, never a module-level singleton, to avoid leaking state between users.useSuspenseQuery (not plain useQuery) for data that should participate in SSR and streaming; reserve useQuery for client-only data.await/return the fetch) and non-blocking (fire-and-forget) prefetching depending on whether the route should wait for that data before responding.try/catch + navigate() needed.queryClient via router context so loaders can access it.notFound() and redirect() are throwable primitives; this integration specifically automates handling redirect() from Query.