Execution contract
- Use when
- Use when a Router or Start app prefetches Query during SSR and needs automatic dehydration, hydration, or streaming.
- Avoid when
- A pure SPA or client-only data does not need SSR serialization; do not add server hydration complexity without value.
- Preconditions
- Confirm getRouter runs per request, QueryClient ownership, critical versus optional query classes, and the fields allowed in browser payloads.
- Verification
- Use concurrent user sessions to verify cache isolation, inspect HTML/stream payloads for secrets, and confirm hydrated queries do not immediately refetch.
- Failure mode
- Cross-request data indicates a module-level QueryClient singleton; an immediate hydration refetch usually means query keys, instances, or staleTime disagree.
- Security
- Dehydrated cache is browser-visible; authorize before fetching, serialize minimum fields, and rely on the officially escaped integration for HTML context.
Instance ownership
Create QueryClient inside getRouter and pass the same instance to Router Context and the integration. Never hoist a user-data QueryClient into a server module singleton. Set a suitable staleTime for SSR data so hydration does not immediately refetch it as stale.
export function getRouter() {
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 30_000 } },
})
const router = createRouter({ routeTree, context: { queryClient } })
setupRouterSsrQueryIntegration({ router, queryClient })
return router
}Loading modes
Return or await ensureQueryData in the loader for critical data. Start fetchQuery without returning or awaiting it for data that may arrive progressively. Use useSuspenseQuery for SSR participation; plain useQuery fetches on the client after hydration. Do not sequentially await independent queries.
Payload audit
Dehydrate only browser-readable data and use shouldDehydrateQuery to exclude explicitly marked entries. Inspect HTML and stream payloads for secrets, server-only fields, or other-user data. Custom serialization must escape HTML context; do not concatenate raw JSON.stringify output.