HydrationBoundary
The HydrationBoundary component is used to hydrate queries that were prefetched on the server into the QueryClient cache on the client side. This is essential for Server-Side Rendering (SSR) scenarios.
Import
import { HydrationBoundary } from '@tanstack/react-query'
Props
state
DehydratedState | null | undefined
required
The dehydrated state from the server. Typically obtained from dehydrate(queryClient) on the server.
Optional hydration optionsDefault options to apply to hydrated queries
Child components that will have access to the hydrated queries
Optional QueryClient instance. If not provided, uses the client from context.
Examples
Next.js App Router (Server Components)
// app/posts/page.tsx
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from '@tanstack/react-query'
import { Posts } from './posts'
export default async function PostsPage() {
const queryClient = new QueryClient()
// Prefetch on the server
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Posts />
</HydrationBoundary>
)
}
// app/posts/posts.tsx (Client Component)
'use client'
import { useQuery } from '@tanstack/react-query'
export function Posts() {
// This query will use the prefetched data
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
return (
<div>
{data?.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
Next.js Pages Router (getServerSideProps)
import {
QueryClient,
dehydrate,
HydrationBoundary,
} from '@tanstack/react-query'
import type { GetServerSideProps } from 'next'
export const getServerSideProps: GetServerSideProps = async () => {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
function PostsPage({ dehydratedState }) {
return (
<HydrationBoundary state={dehydratedState}>
<Posts />
</HydrationBoundary>
)
}
function Posts() {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
return <div>{/* render posts */}</div>
}
export default PostsPage
Remix Loader
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { json, useLoaderData } from '@remix-run/react'
import { HydrationBoundary } from '@tanstack/react-query'
export async function loader() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
return json({ dehydratedState: dehydrate(queryClient) })
}
export default function PostsRoute() {
const { dehydratedState } = useLoaderData<typeof loader>()
return (
<HydrationBoundary state={dehydratedState}>
<Posts />
</HydrationBoundary>
)
}
Multiple Boundaries
import { HydrationBoundary } from '@tanstack/react-query'
function App({ postsState, usersState }) {
return (
<div>
<HydrationBoundary state={postsState}>
<Posts />
</HydrationBoundary>
<HydrationBoundary state={usersState}>
<Users />
</HydrationBoundary>
</div>
)
}
Nested Boundaries
import { HydrationBoundary } from '@tanstack/react-query'
function Layout({ layoutState, children }) {
return (
<HydrationBoundary state={layoutState}>
<Header />
<main>{children}</main>
<Footer />
</HydrationBoundary>
)
}
function PostsPage({ postsState }) {
return (
<Layout layoutState={layoutState}>
<HydrationBoundary state={postsState}>
<Posts />
</HydrationBoundary>
</Layout>
)
}
With Custom Options
import { HydrationBoundary } from '@tanstack/react-query'
function App({ dehydratedState }) {
return (
<HydrationBoundary
state={dehydratedState}
options={{
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
},
},
}}
>
<Posts />
</HydrationBoundary>
)
}
Handling Null State
import { HydrationBoundary } from '@tanstack/react-query'
function App({ dehydratedState }) {
// HydrationBoundary safely handles null/undefined state
return (
<HydrationBoundary state={dehydratedState}>
<Posts />
</HydrationBoundary>
)
}
How It Works
- Server Side: Use
dehydrate(queryClient) to serialize the cache into a plain object
- Transfer: Pass this dehydrated state from server to client (via props, loaders, etc.)
- Client Side:
HydrationBoundary calls hydrate() to restore the cache
- Optimization: New queries are hydrated immediately during render, while existing queries are hydrated in an effect to avoid updating current page data during transitions
Notes
- The
state prop can be null or undefined - the component handles this gracefully
- Hydration only occurs if the query doesn’t already exist in the cache or if the hydrated data is newer
- Multiple
HydrationBoundary components can be used for different parts of your app
- The component performs intelligent hydration timing:
- New queries (not in cache) are hydrated during render
- Existing queries are hydrated in an effect to support React transitions
- If a query already exists with newer data, hydration is skipped for that query
- The component returns children as-is (it’s a transparent wrapper)
- Must be used within a
QueryClientProvider