В контексте TanStack Query гидратация и дегидратация представляют собой механизм переноса состояния кеша между различными средами выполнения приложения. Чаще всего этот механизм используется при серверном рендеринге, статической генерации страниц, предварительной загрузке данных и восстановлении состояния на клиенте.
Дегидратация (dehydrate) — процесс сериализации
состояния QueryClient в обычный JSON-совместимый
объект.
Гидратация (hydrate) — процесс восстановления этого
состояния внутри нового экземпляра QueryClient.
Механизм позволяет:
При обычном клиентском рендеринге последовательность выглядит следующим образом:
useQuery обнаруживает отсутствие кеша.При SSR без гидратации возникает дублирование:
useQuery снова выполняет запрос.В результате:
Гидратация устраняет эту проблему.
Полный цикл выглядит так:
QueryClient.prefetchQuery.dehydrate.QueryClient.hydrate.useQuery получает готовые данные из кеша.Для SSR используется пакет:
npm install @tanstack/react-query
В современных версиях дополнительный пакет гидратации больше не требуется.
Обычно создаётся отдельный экземпляр клиента:
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient()
При SSR нельзя использовать один глобальный клиент для всех запросов сервера.
Каждый HTTP-запрос должен создавать собственный экземпляр
QueryClient.
import {
QueryClient,
dehydrate
} from '@tanstack/react-query'
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
const dehydratedState = dehydrate(queryClient)
После вызова dehydrate создаётся сериализуемое
состояние:
{
queries: [...],
mutations: [...]
}
Этот объект можно:
В сериализованное состояние входят:
queryKey{
queries: [
{
queryKey: ['posts'],
state: {
data: [...],
status: 'success',
fetchStatus: 'idle'
}
}
]
}
TanStack Query автоматически исключает внутренние несериализуемые поля.
На клиенте выполняется восстановление кеша.
import {
QueryClient,
QueryClientProvider,
hydrate
} from '@tanstack/react-query'
const queryClient = new QueryClient()
hydrate(queryClient, dehydratedState)
После гидратации:
useQuery не вызывает загрузку;Современный подход использует HydrationBoundary.
import {
HydrationBoundary
} from '@tanstack/react-query'
Пример:
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={dehydratedState}>
<App />
</HydrationBoundary>
</QueryClientProvider>
HydrationBoundary автоматически вызывает
hydrate.
import {
QueryClient,
dehydrate,
HydrationBoundary
} from '@tanstack/react-query'
export default async function Page() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Posts />
</HydrationBoundary>
)
}
'use client'
import { useQuery } from '@tanstack/react-query'
export function Posts() {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
return (
<div>
{data.map(post => (
<div key={post.id}>
{post.title}
</div>
))}
</div>
)
}
После гидратации запрос повторно не выполняется.
import {
QueryClient,
dehydrate
} from '@tanstack/react-query'
export async function getServerSideProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
return {
props: {
dehydratedState: dehydrate(queryClient)
}
}
}
import {
HydrationBoundary
} from '@tanstack/react-query'
export default function Page({ dehydratedState }) {
return (
<HydrationBoundary state={dehydratedState}>
<Posts />
</HydrationBoundary>
)
}
Можно гидратировать сразу несколько кешей.
await Promise.all([
queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts
}),
queryClient.prefetchQuery({
queryKey: ['users'],
queryFn: fetchUsers
}),
queryClient.prefetchQuery({
queryKey: ['comments'],
queryFn: fetchComments
})
])
После гидратации все данные уже доступны.
Параллельный prefetchQuery критически важен для SSR.
Неправильный вариант:
await queryClient.prefetchQuery(...)
await queryClient.prefetchQuery(...)
await queryClient.prefetchQuery(...)
Правильный вариант:
await Promise.all([
queryClient.prefetchQuery(...),
queryClient.prefetchQuery(...),
queryClient.prefetchQuery(...)
])
Это существенно уменьшает TTFB.
После восстановления кеша TanStack Query проверяет актуальность данных.
Если staleTime равен 0, запрос может
немедленно обновиться.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 1000 * 60
})
В этом случае данные считаются актуальными одну минуту.
Даже после гидратации возможен повторный запрос.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
refetchOnMount: true
})
Чтобы полностью исключить повторный fetch:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 60000,
refetchOnMount: false
})
Можно исключать часть запросов из сериализации.
dehydrate(queryClient, {
shouldDehydrateQuery: query =>
query.queryKey[0] !== 'admin'
})
Это позволяет:
По умолчанию запросы с ошибками не сериализуются.
Поведение можно изменить:
dehydrate(queryClient, {
shouldDehydrateQuery: () => true
})
Объекты Error не сериализуются корректно через JSON.
Проблемный пример:
{
error: new Error('Network failed')
}
После сериализации теряются:
Часто ошибки преобразуют вручную:
{
message: error.message
}
dehydrate работает только с JSON-совместимыми
структурами.
Проблемы вызывают:
MapSetDateBigIntДата превращается в строку.
{
createdAt: "2026-05-24T12:00:00.000Z"
}
После гидратации:
typeof createdAt === 'string'
Для восстановления:
new Date(createdAt)
Иногда используется superjson.
npm install superjson
Пример:
import superjson from 'superjson'
const serialized = superjson.serialize(
dehydrate(queryClient)
)
Восстановление:
const dehydratedState =
superjson.deserialize(serialized)
Это позволяет сохранять:
DateMapSetawait queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
Не выбрасывает ошибку наружу.
await queryClient.fetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
Возвращает данные и выбрасывает ошибки.
fetchQuery полезен:
Infinite Query полностью поддерживает дегидратацию.
await queryClient.prefetchInfiniteQuery({
queryKey: ['feed'],
queryFn: fetchFeed,
initialPageParam: 0
})
Сохраняются:
{
pages: [...],
pageParams: [...]
}
После гидратации пагинация продолжает работать без повторной загрузки.
TanStack Query способен сериализовать mutation cache.
Это полезно:
Гидратация тесно связана с persistence.
Например:
npm install @tanstack/query-persist-client-core
Кеш может сохраняться:
После перезапуска приложения выполняется hydrate из persistence layer.
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
<App />
</PersistQueryClientProvider>
Гидратация особенно важна для offline-режима.
Сценарий:
Одна из самых опасных ошибок — повторное использование
QueryClient между запросами.
Неправильно:
const queryClient = new QueryClient()
На уровне модуля сервера.
Проблемы:
Экземпляр должен создаваться внутри запроса.
export async function getServerSideProps() {
const queryClient = new QueryClient()
// ...
}
Иногда требуется ручная очистка.
queryClient.clear()
Особенно актуально:
Большой кеш может значительно увеличивать HTML.
Проблемы:
shouldDehydrateQuery
Лучше:
{
id,
title
}
Хуже:
{
hugeNestedObject
}
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
select: data =>
data.map(post => ({
id: post.id,
title: post.title
}))
})
Hydration отлично работает вместе с Suspense.
useSuspenseQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
Если данные уже гидратированы:
В React Server Components возможна частичная потоковая передача.
TanStack Query постепенно интегрируется с streaming SSR.
Это позволяет:
Пример серверной загрузки:
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
return json({
dehydratedState: dehydrate(queryClient)
})
При гибридных архитектурах гидратация позволяет:
Devtools позволяют наблюдать:
После SSR запросы сразу появляются в кеше.
Причина:
staleTime: 0
Причина:
Причина:
Причина:
Признаки успешной гидратации:
useQuery({
initialData: data
})
Передаёт данные локально.
hydrate(queryClient, state)
Восстанавливает полноценный кеш.
Hydrate сохраняет:
initialData подходит:
Hydration необходим: