Skip to Content
Documentation@suspensive/react-query<QueriesHydrationBoundary/>

QueriesHydrationBoundary

<QueriesHydrationBoundary/> is an experimental feature, so this interface may change.

A component that prefetches multiple queries on the server and automatically hydrates them to client components.

This is useful when you want to prefetch data on the server side and pass it to the client in React Server Components environments.

💡 Recommended reading: Before using this component, we recommend reading the TanStack Query Advanced Server Rendering guide , which covers Server Components, streaming, and the Next.js App Router. Understanding these concepts will help you use QueriesHydrationBoundary more effectively.

Basic Usage

import { QueriesHydrationBoundary } from '@suspensive/react-query' import { Suspense } from '@suspensive/react' import { userQueryOptions, postsQueryOptions } from './queries' import { UserProfile, PostList } from './_components' // Server Component const PostsPage = ({ userId }) => { return ( <> <Suspense fallback={<div>Loading user...</div>}> <QueriesHydrationBoundary queries={[userQueryOptions(userId)]}> <UserProfile userId={userId} /> </QueriesHydrationBoundary> </Suspense> <Suspense fallback={<div>Loading posts...</div>}> <QueriesHydrationBoundary queries={[postsQueryOptions(userId)]}> <PostList userId={userId} /> </QueriesHydrationBoundary> </Suspense> </> ) }

Props

queries

An array of queries to be prefetched on the server. Each query must have a queryKey.

type QueriesHydrationBoundaryProps = { queries: WithRequired<QueryOptions<any, any, any, any>, 'queryKey'>[] children: ReactNode queryClient?: QueryClient // Optional (default: new QueryClient()) skipSsrOnError?: boolean | { fallback: ReactNode } // Optional (default: true) }

queryClient

Optionally, you can pass a QueryClient instance to use. If not provided, a new QueryClient instance will be created.

import { QueryClient } from '@tanstack/react-query' import { QueriesHydrationBoundary } from '@suspensive/react-query' const queryClient = new QueryClient() const Page = async () => { return ( <QueriesHydrationBoundary queryClient={queryClient} queries={[]}> {/* ... */} </QueriesHydrationBoundary> ) }

skipSsrOnError

Controls the behavior when an error occurs while fetching queries on the server. This option becomes clearer when you understand the 3 stages where data fetching occurs:

  1. RSC (React Server Component): Query execution in QueriesHydrationBoundary
  2. RCC (React Client Component) - Server: Query execution in useSuspenseQuery if no cached data exists
  3. RCC (React Client Component) - Browser: Query execution in useSuspenseQuery if no cached or fresh data exists

If stage 1 fails, stage 2 is likely to fail for the same reason. However, stage 3 (browser) might succeed (e.g., authentication-related issues, etc.). Since the browser can retry and potentially succeed, it’s better to use skipSsrOnError to skip RCC(server) and fetch directly from RCC(browser) when RSC fails.

Important: When stage 1 succeeds (no error), the data is already hydrated to the client. In this case, you can verify in the browser’s Network tab that no additional fetch requests are made in stages 2 and 3, as the data is served from the cache. This demonstrates the efficiency of server-side prefetching with QueriesHydrationBoundary.

  • true (default): If stage 1 fails, skip SSR and retry in stage 3 (browser)
  • false: Even if stage 1 fails, proceed to stage 2 without hydration (retry fetching on server)
  • { fallback: ReactNode }: If stage 1 fails, skip SSR and display a custom fallback UI while moving to stage 3
import { QueriesHydrationBoundary } from '@suspensive/react-query' import { Suspense } from '@suspensive/react' import { UserProfile, PostList, CommentList } from './_components' const Page = async () => { return ( <> {/* Skip SSR and retry in browser if stage 1 fails (default behavior) */} <Suspense fallback={<div>Loading...</div>}> <QueriesHydrationBoundary queries={[userQuery]}> <UserProfile /> </QueriesHydrationBoundary> </Suspense> {/* Skip SSR and retry in browser with custom fallback if stage 1 fails */} <Suspense fallback={<div>Loading...</div>}> <QueriesHydrationBoundary queries={[postsQuery]} skipSsrOnError={{ fallback: <div>Unable to fetch data from server...</div>, }} > <PostList /> </QueriesHydrationBoundary> </Suspense> {/* Retry in stage 2 (RCC server) even if stage 1 fails */} <Suspense fallback={<div>Loading...</div>}> <QueriesHydrationBoundary queries={[commentsQuery]} skipSsrOnError={false} > <CommentList /> </QueriesHydrationBoundary> </Suspense> </> ) }

Motivation: Simplify prefetching multiple queries in Server Components

In React Server Components environments, you can prefetch data on the server to eliminate initial loading states. However, manually prefetching and dehydrating multiple queries is cumbersome.

Traditional approach: Manual prefetch and dehydrate

import { QueryClient, dehydrate, HydrationBoundary, } from '@tanstack/react-query' import { Suspense } from '@suspensive/react' import { userQueryOptions, postsQueryOptions } from './queries' import { UserProfile, PostList } from './_components' // Server Component const PostsPage = ({ userId }) => { return ( <> <Suspense fallback={<div>Loading user...</div>}> <UserProfileWithData userId={userId} /> </Suspense> <Suspense fallback={<div>Loading posts...</div>}> <PostListWithData userId={userId} /> </Suspense> </> ) } // Need to separate each server component for HTML Streaming const UserProfileWithData = async ({ userId }) => { const queryClient = new QueryClient() try { await queryClient.ensureQueryData(userQueryOptions(userId)) } catch (error) { return ( // If queryClient.ensureQueryData fails, use ClientOnly to prevent SSR and render on the browser directly <ClientOnly fallback={<div>Loading user...</div>}> <UserProfile userId={userId} /> </ClientOnly> ) } return ( <HydrationBoundary state={dehydrate(queryClient)}> <UserProfile userId={userId} /> </HydrationBoundary> ) } const PostListWithData = async ({ userId }) => { const queryClient = new QueryClient() try { await queryClient.ensureQueryData(postsQueryOptions(userId)) } catch (error) { return ( // If queryClient.ensureQueryData fails, use ClientOnly to prevent SSR and render on the browser directly <ClientOnly fallback={<div>Loading posts...</div>}> <PostList userId={userId} /> </ClientOnly> ) } return ( <HydrationBoundary state={dehydrate(queryClient)}> <PostList userId={userId} /> </HydrationBoundary> ) }

Using QueriesHydrationBoundary

With <QueriesHydrationBoundary/>, all of this is handled automatically:

import { QueriesHydrationBoundary } from '@suspensive/react-query' import { Suspense } from '@suspensive/react' import { userQueryOptions, postsQueryOptions } from './queries' import { UserProfile, PostList } from './_components' // Server Component const PostsPage = ({ userId }) => { return ( <> <Suspense fallback={<div>Loading user...</div>}> <QueriesHydrationBoundary queries={[userQueryOptions(userId)]}> <UserProfile userId={userId} /> </QueriesHydrationBoundary> </Suspense> <Suspense fallback={<div>Loading posts...</div>}> <QueriesHydrationBoundary queries={[postsQueryOptions(userId)]}> <PostList userId={userId} /> </QueriesHydrationBoundary> </Suspense> </> ) }

Key benefits:

  1. Concise code: Automates QueryClient creation, prefetch, and dehydrate processes
  2. Parallel data fetching: Uses Promise.all to process all queries in parallel
  3. Type safety: Requires queryKey to prevent mistakes
  4. Consistent pattern: Handles multiple queries consistently

Example

Here’s a live example demonstrating QueriesHydrationBoundary with Next.js streaming:

Next.js Streaming React Query Example

Tip: In the live demo, check the “4. no error (Best Practice)” case and open the browser’s Developer Tools Network tab. You’ll notice that no fetch requests are made because the data was successfully prefetched on the server and hydrated to the client, demonstrating the efficiency of QueriesHydrationBoundary.

Disabling SSR

If you want to skip server-side rendering for a specific component, simply add the clientOnly prop to <Suspense/>. In this case, since you don’t need to prefetch data on the server, you don’t need QueriesHydrationBoundary either:

import { QueriesHydrationBoundary } from '@suspensive/react-query' import { Suspense } from '@suspensive/react' import { userQueryOptions, postsQueryOptions } from './queries' import { UserProfile, PostList } from './_components' const PostsPage = ({ userId }) => { return ( <> {/* UserProfile skips SSR and only renders on the client */} <Suspense clientOnly fallback={<div>Loading user...</div>}> <UserProfile userId={userId} /> </Suspense> {/* PostList is prefetched and rendered on the server */} <Suspense fallback={<div>Loading posts...</div>}> <QueriesHydrationBoundary queries={[postsQueryOptions(userId)]}> <PostList userId={userId} /> </QueriesHydrationBoundary> </Suspense> </> ) }

When using the clientOnly prop:

  • Components within that Suspense boundary will not be rendered on the server
  • Data will only be fetched and rendered on the client
  • You don’t need QueriesHydrationBoundary since server-side prefetch is not needed

Version Differences

@tanstack/react-query v5

In @tanstack/react-query v5, it uses the HydrationBoundary component.

import { HydrationBoundary } from '@tanstack/react-query' import { QueriesHydrationBoundary } from '@suspensive/react-query' // QueriesHydrationBoundary internally uses HydrationBoundary

@tanstack/react-query v4

In @tanstack/react-query v4, it uses the Hydrate component.

import { Hydrate } from '@tanstack/react-query' import { QueriesHydrationBoundary } from '@suspensive/react-query' // QueriesHydrationBoundary internally uses Hydrate

Important Notes

  • This component is an async server component.
  • It can only be used in frameworks that support React Server Components (Next.js 13+ App Router, etc.).
  • All queries must include a queryKey.

Version History

VersionChanges
v2.17.0<QueriesHydrationBoundary/> has been added as an experimental feature.
Last updated on