Что такое hydration в React и какие ошибки hydration mismatch возникают?
Что такое hydration
Hydration (гидратация) — механизм, с помощью которого React "оживляет" статический HTML, пришедший с сервера при SSR или SSG. Вместо того чтобы удалить существующую разметку и построить DOM заново, React проходит по уже отрендеренным узлам, сопоставляет их со своим виртуальным деревом и, если всё совпадает, просто навешивает обработчики событий и связывает узлы с внутренним состоянием компонентов.
Как это работает на практике
- Сервер рендерит компоненты в HTML через
renderToStringилиrenderToPipeableStream. - Браузер сразу показывает готовую разметку — пользователь видит контент до загрузки JS, это улучшает FCP и LCP.
- После загрузки бандла вызывается
hydrateRoot(React 18+) вместоcreateRoot. - React проверяет соответствие DOM-дерева и того, что получилось бы при обычном клиентском рендере, и переиспользует существующие узлы.
Что такое hydration mismatch
Если результат серверного и клиентского рендера расходится, React обнаруживает несовпадение во время гидратации. В dev-режиме это выводит предупреждение в консоль вида "Hydration failed because the initial UI does not match what was rendered on the server". React пытается восстановить согласованность, отбрасывая часть серверной разметки и перерендеривая узел на клиенте — это может вызвать заметное "мигание" контента и лишнюю работу браузера.
Частые причины mismatch
- Недетерминированные значения в рендере:
Date.now(),Math.random(),new Date()— на сервере и клиенте они разные. - Обращение к
window,localStorage,navigatorпрямо в теле компонента при первом рендере. - Форматирование дат и чисел, зависящее от локали или часового пояса окружения (сервер и браузер пользователя могут отличаться).
- Паттерн
typeof window !== 'undefined'для условного рендера — на сервере и клиенте условие даёт разный результат. - Невалидная вложенность HTML (
<p>внутри<p>,<div>внутри<span>) — браузер молча исправляет такую разметку при парсинге, и итоговый DOM отличается от того, что построил сервер. - Расширения браузера, которые вставляют атрибуты в
<body>до гидратации (классический пример — Grammarly). - Гонка данных: сервер и клиент получают данные в разное время, и ответ API успел измениться.
Как избежать mismatch
- Не использовать источники недетерминированных значений в самом рендере — вычислять их в
useEffectи обновлять состояние уже после монтирования. - Для контента, который обязан отличаться на клиенте (локальное время, значения из
localStorage), рендерить на сервере нейтральную заглушку и подменять её вuseEffect. - Точечно подавлять безопасные различия через
suppressHydrationWarning(например, для метки времени), но не использовать это как способ скрыть реальные баги. - Следить за валидностью HTML-вложенности, особенно при использовании кастомных компонентов, которые рендерят блочные элементы.
- Гарантировать, что данные для SSR и первого клиентского рендера идентичны — передавать initial state через props или дегидратированный кэш (например, React Query
dehydrate/hydrate).
Итог
Hydration позволяет совместить быстрый первый рендер SSR с интерактивностью клиентского React, но требует, чтобы разметка на сервере и клиенте была детерминированной. Mismatch — почти всегда сигнал, что рендер зависит от окружения (время, случайность, доступность браузерных API), и лечится вынесением такой логики за пределы первого рендера.
Что хочет услышать интервьюер
Кандидат объясняет, что hydration переиспользует серверный HTML, а не перестраивает DOM заново
Понимание разницы между hydrateRoot и createRoot
Умение назвать конкретные причины mismatch (Date.now, window, невалидный HTML)
Знание практических способов избежать mismatch (useEffect, suppressHydrationWarning)
Понимание последствий mismatch: предупреждения, лишний перерендер, мигание UI
Пример: Причина mismatch: недетерминированное значение в рендере
// Плохо: Date.now() даёт разные значения на сервере и клиенте
function Timestamp() {
return <span>{Date.now()}</span>;
}
// React выдаст предупреждение о hydration mismatch,
// потому что серверный HTML и клиентский рендер не совпадут
Пример: Исправление через useEffect
function Timestamp() {
// На сервере и при первом клиентском рендере используем заглушку
const [time, setTime] = useState<number | null>(null);
useEffect(() => {
// Значение выставляем уже после монтирования на клиенте
setTime(Date.now());
}, []);
return <span>{time ?? '—'}</span>;
}
Пример: Точечное подавление ожидаемого различия
function LastVisit({ isoDate }: { isoDate: string }) {
// Форматирование зависит от локали браузера пользователя,
// поэтому сервер и клиент могут дать разный текст — это ожидаемо
return (
<time suppressHydrationWarning>
{new Date(isoDate).toLocaleString()}
</time>
);
}
Типичные ошибки
Путают hydration с обычным клиентским рендером и не видят разницы в поведении React
Не могут назвать ни одной конкретной причины mismatch, кроме общих слов
Предлагают suppressHydrationWarning как универсальное решение для любых расхождений
Не знают, что обращение к window/localStorage в теле компонента при первом рендере ломает SSR
Считают, что невалидная HTML-вложенность не влияет на гидратацию


