with one click
tanstack-query-ssr
// Server-side rendering and hydration patterns for TanStack Query v5 — dehydrate cache on server, hydrate on client with HydrationBoundary, and avoid SSR/CSR data mismatches.
// Server-side rendering and hydration patterns for TanStack Query v5 — dehydrate cache on server, hydrate on client with HydrationBoundary, and avoid SSR/CSR data mismatches.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | tanstack-query-ssr |
| description | Server-side rendering and hydration patterns for TanStack Query v5 — dehydrate cache on server, hydrate on client with HydrationBoundary, and avoid SSR/CSR data mismatches. |
| tech_stack | ["react"] |
| language | typescript |
| capability | ["data-fetching","state-management"] |
| version | TanStack Query v5 |
| collected_at | "2025-01-01T00:00:00.000Z" |
Source: https://tanstack.com/query/latest/docs/framework/react/reference/hydration
TanStack Query's SSR primitives allow you to prefetch data on the server, serialize the entire QueryClient cache, and rehydrate it on the client — eliminating loading spinners on first render and making pre-populated content SEO-friendly.
ReactQueryStreamedHydration).dehydrate/hydrate to/from localStorage or similar.// pages/posts.tsx
import { dehydrate, HydrationBoundary, QueryClient, useQuery } from '@tanstack/react-query'
export async function getServerSideProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: fetchPosts })
return { props: { dehydratedState: dehydrate(queryClient) } }
}
export default function Posts({ dehydratedState }) {
return (
<HydrationBoundary state={dehydratedState}>
<PostsList />
</HydrationBoundary>
)
}
function PostsList() {
const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
return <div>{data.map(p => <Post key={p.id} {...p} />)}</div>
}
The key sequence:
QueryClient per requestprefetchQuery (or prefetchInfiniteQuery) to populate itdehydrate(queryClient) to produce a serializable state snapshot<HydrationBoundary state={...}>dehydrate(queryClient, options?)Serializes the QueryClient cache into a DehydratedState. Only successful queries are included by default.
const state = dehydrate(queryClient, {
shouldDehydrateQuery: (q) => true, // include errors/pending too
shouldDehydrateMutation: (m) => true, // include mutations (default: paused only)
serializeData: (data) => data, // transform data before serialization
shouldRedactErrors: () => false, // set false to include error details
})
hydrate(queryClient, dehydratedState, options?)Programmatically merges dehydrated state into a cache. Only overwrites if dehydrated data is newer than what's already cached.
hydrate(queryClient, dehydratedState, {
defaultOptions: { queries: { staleTime: 60_000 } },
deserializeData: (data) => data,
})
<HydrationBoundary state={dehydratedState} options? />React component — the idiomatic way to hydrate on the client. Reads QueryClient from context via useQueryClient(). Only hydrates queries, not mutations.
shouldDehydrateQuery: () => true to include pending/error states.hydrate() directly if you need mutation state.shouldRedactErrors. Set () => false to preserve them across the wire.localStorage or similar.staleTime/gcTime cause unnecessary refetches after hydration.ReactQueryStreamedHydration for progressive hydration with streaming. Prefetch in Server Components, wrap client tree in both QueryClientProvider and HydrationBoundary.@tanstack/react-query's dedicated Next.js helpers or a useState lazy initializer to avoid sharing state.localStorage persistence: combine dehydrate/hydrate with persistQueryClient plugin rather than hand-rolling serialization.<HydrationBoundary> inside <Suspense> when using streaming to show fallbacks for queries that haven't resolved yet.