Skip to main content
The HydrationBoundary component is used to hydrate dehydrated query data on the client. This is essential for server-side rendering (SSR) with TanStack Query.

Import

import { HydrationBoundary } from '@tanstack/react-query'

Signature

function HydrationBoundary({
  state,
  options,
  children,
  queryClient,
}: HydrationBoundaryProps): React.ReactElement

Props

state
DehydratedState | null | undefined
required
The dehydrated state object obtained from dehydrate(queryClient) on the server.
interface DehydratedState {
  queries: DehydratedQuery[]
  mutations?: DehydratedMutation[]
}
options
HydrateOptions
Optional hydration options.
children
React.ReactNode
The React components that will have access to the hydrated data.
queryClient
QueryClient
Optional QueryClient instance. If not provided, uses the QueryClient from context.

Examples

Basic Usage with Next.js App Router

import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from '@tanstack/react-query'
import { TodoList } from './TodoList'

export default async function TodosPage() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <TodoList />
    </HydrationBoundary>
  )
}

Client Component Using Hydrated Data

'use client'

import { useQuery } from '@tanstack/react-query'

export function TodoList() {
  const { data } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  // Data is immediately available from hydration
  return (
    <ul>
      {data?.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

With Next.js Pages Router

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { GetServerSideProps } from 'next'
import { TodoList } from '../components/TodoList'

export const getServerSideProps: GetServerSideProps = async () => {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  }
}

interface PageProps {
  dehydratedState: any
}

export default function TodosPage({ dehydratedState }: PageProps) {
  return (
    <HydrationBoundary state={dehydratedState}>
      <TodoList />
    </HydrationBoundary>
  )
}

With Custom Options

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'

export default async function Page() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  return (
    <HydrationBoundary
      state={dehydrate(queryClient)}
      options={{
        defaultOptions: {
          queries: {
            staleTime: 1000 * 60 * 5, // 5 minutes
          },
        },
      }}
    >
      <TodoList />
    </HydrationBoundary>
  )
}

Multiple Hydration Boundaries

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { TodoList } from './TodoList'
import { UserProfile } from './UserProfile'

export default async function DashboardPage() {
  const todosClient = new QueryClient()
  const userClient = new QueryClient()

  await Promise.all([
    todosClient.prefetchQuery({
      queryKey: ['todos'],
      queryFn: fetchTodos,
    }),
    userClient.prefetchQuery({
      queryKey: ['user'],
      queryFn: fetchUser,
    }),
  ])

  return (
    <div>
      <HydrationBoundary state={dehydrate(userClient)}>
        <UserProfile />
      </HydrationBoundary>
      <HydrationBoundary state={dehydrate(todosClient)}>
        <TodoList />
      </HydrationBoundary>
    </div>
  )
}

With Infinite Queries

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { ProjectList } from './ProjectList'

export default async function ProjectsPage() {
  const queryClient = new QueryClient()

  await queryClient.prefetchInfiniteQuery({
    queryKey: ['projects'],
    queryFn: ({ pageParam }) => fetchProjects(pageParam),
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <ProjectList />
    </HydrationBoundary>
  )
}

Conditional Hydration

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'

interface PageProps {
  userId?: string
}

export default async function UserPage({ userId }: PageProps) {
  const queryClient = new QueryClient()

  if (userId) {
    await queryClient.prefetchQuery({
      queryKey: ['user', userId],
      queryFn: () => fetchUser(userId),
    })
  }

  return (
    <HydrationBoundary state={userId ? dehydrate(queryClient) : null}>
      <UserProfile userId={userId} />
    </HydrationBoundary>
  )
}

With Streaming (React Server Components)

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { Suspense } from 'react'

export default async function StreamingPage() {
  const queryClient = new QueryClient()

  // Prefetch critical data
  await queryClient.prefetchQuery({
    queryKey: ['critical-data'],
    queryFn: fetchCriticalData,
  })

  // Don't await non-critical data
  queryClient.prefetchQuery({
    queryKey: ['non-critical-data'],
    queryFn: fetchNonCriticalData,
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <div>
        <CriticalComponent />
        <Suspense fallback={<div>Loading...</div>}>
          <NonCriticalComponent />
        </Suspense>
      </div>
    </HydrationBoundary>
  )
}

Behavior

The HydrationBoundary component:
  1. Hydrates the dehydrated state into the QueryClient cache
  2. Intelligently handles queries that already exist in the cache
  3. For new queries, hydrates them immediately during render
  4. For existing queries, defers hydration until after render (to avoid updating during transitions)
  5. Compares timestamps to determine if hydrated data is newer than cached data
  6. Works seamlessly with React Suspense and Server Components

Notes

HydrationBoundary must be used within a QueryClientProvider component.
The state prop can be null or undefined, in which case no hydration occurs.
Hydration happens during render for new queries and after render for existing queries to support React transitions.
When using with Next.js App Router, the server component should create a new QueryClient and pass the dehydrated state to the client component.
The dehydrated state is serializable and can be passed through Next.js props or other serialization boundaries.

Build docs developers (and LLMs) love