Server-Side Rendering (SSR) та Гідратація
Server-Side Rendering (SSR) та Гідратація
Якщо ви використовуєте Next.js, Remix або Astro, ви хочете, щоб ваші дані завантажувалися на сервері для SEO та швидкого першого відтворення (LCP).
TanStack Query має для цього потужний механізм, який називається Hydration (Гідратація).
Проблема: Сервер vs Клієнт
- Server: Ми робимо запит до БД/API, отримуємо дані, рендеримо HTML.
- Client: Браузер завантажує JS, React запускається ("гідрується").
- Problem: Якщо React Query на клієнті не знає про дані, які завантажив сервер, він почне завантажувати їх знову (
isPending: true). Користувач побачить "мерехтіння" (HTML з даними -> Loading -> HTML з даними).
Нам треба передати "стан" з сервера на клієнт.
Два підходи
1. initialData (Простий, але обмежений)
Ви можете передати дані як пропси.
// Next.js Page (Server Component або getServerSideProps)
export default async function Page() {
const posts = await getPosts();
// Передаємо posts в клієнтський компонент
return <PostsList initialPosts={posts} />;
}
// Client Component
'use client';
function PostsList({ initialPosts }) {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: getPosts,
initialData: initialPosts, // Використовуємо дані з сервера
});
}
Мінуси:
- Треба прокидати пропси глибоко по дереву ("prop drilling").
- Якщо
PostsListзнаходиться глибоко, це незручно.
2. Hydration (Рекомендований, Потужний)
Цей метод дозволяє "попередньо заповнити" кеш QueryClient на сервері, а потім "відновити" його на клієнті.
В Next.js App Router це виглядає так:
Крок 1: Створіть getQueryClient (Singleton per request)
На сервері ми не можемо мати один глобальний клієнт, бо запити різних користувачів змішаються. Нам потрібен новий клієнт для кожного запиту.
import { QueryClient } from '@tanstack/react-query';
import { cache } from 'react'; // React cache for memoization per request
const createQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
});
export const getQueryClient = cache(() => createQueryClient());
Крок 2: Prefetching на Сервері
Ми використовуємо HydrationBoundary та dehydrate.
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
import { getQueryClient } from '@/lib/get-query-client';
import { getPosts } from '@/api/posts';
import PostsList from './posts-list';
export default async function PostsPage() {
const queryClient = getQueryClient();
// 1. Prefetch даних на сервері
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: getPosts,
});
// 2. Дегідратація (перетворення стану в JSON)
const dehydratedState = dehydrate(queryClient);
return (
// 3. Передача стану в Boundary
<HydrationBoundary state={dehydratedState}>
<PostsList />
</HydrationBoundary>
);
}
Крок 3: Клієнтський Компонент
Він нічого не знає про SSR. Він просто викликає useQuery.
'use client';
import { useQuery } from '@tanstack/react-query';
import { getPosts } from '@/api/posts';
export default function PostsList() {
// Цей хук миттєво поверне дані, бо вони вже є в HydrationBoundary!
// Ніякого loading state, ніякого мерехтіння.
const { data } = useQuery({
queryKey: ['posts'],
queryFn: getPosts,
});
return <div>{data.map(p => <div key={p.id}>{p.title}</div>)}</div>;
}
staleTime на Сервері
Важливий нюанс: на сервері час виконання миттєвий. Але коли дані доходять до клієнта, проходить час.
Рекомендується встановлювати staleTime більше 0 (наприклад, 1 хвилину), щоб клієнт одразу після завантаження не почав робити refetch.
Якщо staleTime: 0 (default), то:
- Сервер відрендерив HTML з даними.
- Клієнт гідрувався.
- Query побачив, що дані "stale" (бо 0 ms).
- Query одразу робить новий запит на сервер.
Це вбиває сенс SSR. Ставте staleTime: 60 * 1000.
Серіалізація
Пам'ятайте, що дані, які передаються з сервера на клієнт (через dehydrate), повинні бути серіалізованими (JSON).
Dateоб'єкти перетворяться на рядки.Function,Map,Setне передадуться.
Якщо вам потрібні Date об'єкти на клієнті, ви повинні відновлювати їх (парсити) всередині queryFn або select.
Архітектура та Best Practices
Ми вивчили API. Тепер поговоримо про те, як не перетворити проект на смітник. TanStack Query дає багато свободи, але "з великою силою приходить велика відповідальність".
Стратегії Тестування
Тестування асинхронного коду — це біль. "Flaky tests" (тести, що падають через раз), тайм-аути, проблеми з API...