Prefetch critical data in a loader and read the same cache entry in the component.
Define the query contract once
queryOptions keeps queryKey and queryFn together while preserving inference. The loader and component should reuse the same contract so one resource is not fetched under unrelated keys.
import { queryOptions } from '@tanstack/react-query'
export const issuesQuery = queryOptions({
queryKey: ['issues'],
queryFn: () => getIssues(),
staleTime: 30_000,
})Fill before navigation, read during render
ensureQueryData reuses available cache data or waits for the request. useSuspenseQuery then reads the same entry; the SSR integration dehydrates server state and restores it in the browser.
import { useSuspenseQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
import { issuesQuery } from '~/features/issues/queries'
export const Route = createFileRoute('/issues/')({
loader: ({ context }) =>
context.queryClient.ensureQueryData(issuesQuery),
component: IssuesPage,
})
function IssuesPage() {
const { data } = useSuspenseQuery(issuesQuery)
return <p>{data.length} issues</p>
}Turn this lesson into a verifiable skill
The route loader prefetches with the same queryOptions consumed by the component, without a second query-key definition.
Create an issue-detail queryOptions factory and warm it from the detail route loader using issueId.
Initial navigation should issue one detail request; returning to the route should follow the configured freshness policy.
- Using structurally different query keys in loader and component.
- Awaiting independent data in the loader that is not needed for first paint.