---
id: ssr-query-streaming
track: learn
product: query
locale: en
order: 16
revision: 1
sourceCheckedOn: "2026-08-03"
versionRange: "Router ^1 / Query ^5"
verifiedAgainst: "@tanstack/react-router@1.170.18 + @tanstack/react-router-ssr-query@1.167.1 + @tanstack/react-query@5.101.4"
contentModel: 2
prerequisites:
  - "Loader + Query cache lesson completed"
  - "TanStack Router SSR or TanStack Start enabled"
sourceRefs:
  - "https://tanstack.com/router/latest/docs/integrations/query"
  - "https://tanstack.com/query/latest/docs/framework/react/guides/ssr"
---

# Isolate and stream Query state across SSR

> Use the Router SSR Query integration for dehydration, hydration, and streaming while creating an isolated cache per server request.

**Outcome:** Choose blocking or streaming queries, prevent cross-user cache leakage, and verify hydration does not immediately duplicate requests.

## One QueryClient per request

A module-level QueryClient on the server can share cache across requests and users. Create it inside getRouter, then pass the same instance to Router Context and setupRouterSsrQueryIntegration. The integration owns initial dehydration, client hydration, and subsequent query-result streaming.

File: `src/router.tsx`

```tsx
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'

export function getRouter() {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { staleTime: 30_000 } },
  })
  const router = createRouter({
    routeTree,
    context: { queryClient },
  })

  setupRouterSsrQueryIntegration({ router, queryClient })
  return router
}
```

## Choose what blocks and what streams

Returning or awaiting ensureQueryData in a loader makes critical data available before route entry. Starting fetchQuery without awaiting lets the result stream during initial rendering. useSuspenseQuery participates in SSR and streaming, while plain useQuery begins on the client after hydration. Choose by page value instead of blocking on every request.

## SSR payloads are visible to the browser

Dehydrated cache enters HTML or streamed responses, so it must not contain unauthorized data, secrets, or server-only fields. Custom serialization must escape the HTML context correctly; prefer the framework integration over concatenating JSON.stringify output into a script.

> **Note:** Give SSR queries a staleTime above zero so freshly hydrated data does not immediately refetch as stale.

## Practice and verification

### Completion checkpoint

Every SSR request receives an isolated QueryClient, critical queries hydrate, optional queries can stream, and hydration does not immediately refetch.

### Exercise

Block on the issue summary, stream the activity feed, and use two concurrent sessions to prove cached data never crosses users.

### Verification

Inspect server logs, HTML/stream payload, and the browser network panel; initial data fetches once, serialized state excludes private fields, and two users see isolated data.

### Common pitfalls

- Reusing a global module-level QueryClient on the server.
- Dehydrating secrets or unauthorized fields into a browser-visible payload.

## Official sources

- https://tanstack.com/router/latest/docs/integrations/query
- https://tanstack.com/query/latest/docs/framework/react/guides/ssr
