---
id: query.loader-prefetch
kind: recipe
product: query
framework: react
locale: en
revision: 2
sourceCheckedOn: "2026-08-03"
versionRange: "Query ^5 / Router ^1"
verifiedAgainst: "@tanstack/react-query@5.101.4 + @tanstack/react-router@1.170.18 + @tanstack/react-router-ssr-query@1.167.1"
contentModel: 2
packages:
  - "@tanstack/react-query"
  - "@tanstack/react-router"
  - "@tanstack/react-router-ssr-query"
tasks:
  - "prefetch-query-in-loader"
  - "ssr-hydration"
  - "avoid-request-waterfall"
sourceRefs:
  - "https://tanstack.com/router/latest/docs/integrations/query"
---

# Prefetch Query from a route loader

> Create a request-scoped QueryClient for SSR and reuse one queryOptions contract in the loader and useSuspenseQuery.

## Execution contract

- **Use when:** Use when a route should warm Query data before entry and the component will observe the same cache entry.
- **Avoid when:** Do not block a loader on expensive, rarely visited, or non-critical data.
- **Preconditions:** Create a shared queryOptions factory and include every route input affecting the query key in loader deps.
- **Verification:** Navigation issues one request, the component hits the same cache, and parameter changes create a distinct predictable key.
- **Failure mode:** If the component refetches immediately, compare full query keys, freshness settings, and QueryClient identity.
- **Security:** Prefetching does not hide responses; never place unauthorized data in serialized cache or SSR payloads.

## Required integration

Create QueryClient inside getRouter, expose it through Router context, then call setupRouterSsrQueryIntegration. getRouter runs per SSR request; do not hoist QueryClient into a module singleton.

File: `src/router.tsx`

```tsx
const queryClient = new QueryClient()
const router = createRouter({
  routeTree,
  context: { queryClient },
})

setupRouterSsrQueryIntegration({ router, queryClient })
```

## Blocking critical data

For render-critical data, return the ensureQueryData promise from the loader. Call useSuspenseQuery with the same queryOptions in the component. Router waits for navigation, Query owns cache, and the SSR integration handles dehydration and hydration.

File: `src/routes/issues/index.tsx`

```tsx
const issuesQuery = queryOptions({
  queryKey: ['issues'],
  queryFn: getIssues,
})

export const Route = createFileRoute('/issues/')({
  loader: ({ context }) =>
    context.queryClient.ensureQueryData(issuesQuery),
  component: () => {
    const { data } = useSuspenseQuery(issuesQuery)
    return <IssueList issues={data} />
  },
})
```

## Verification checklist

Confirm root Router context types QueryClient, no QueryClient is shared across SSR requests, loader and component use an identical queryKey, and the hydrated page does not repeat its initial browser request.

## Official sources

- https://tanstack.com/router/latest/docs/integrations/query
