Hydration в React: что происходит после SSR и где ломается интерактивность
Разбираем hydration в React после SSR: как браузер связывает HTML с деревом React, почему возникают hydration mismatch, сколько стоит гидрация и как уменьшить клиентскую нагрузку.
- Введение
- Что такое hydration без упрощений
- Что происходит после SSR по шагам
- 1. Браузер получает HTML и начинает парсинг
- 2. Загружается клиентский JavaScript
- 3. React строит клиентское дерево и пытается его привязать к DOM
- 4. Подключаются события и состояние
- 5. Дерево становится обычным клиентским React-деревом
- Архитектурный разбор: где именно находится гидрация в общем pipeline
- Базовый пример: сервер и клиент совпадают
- Почему возникает hydration mismatch
- 1. Нестабильные значения в рендере
- 2. Условный JSX по browser API
- 3. Разные данные на сервере и клиенте
- 4. Некорректная работа с формами
- Как исправлять mismatch без костылей
- Selective hydration: почему React не обязан оживлять все строго по очереди
- Сравнение стратегий: CSR, SSR + hydration, SSR + узкие client islands
- Production pitfalls: где гидрация чаще всего становится дорогой
- 1. Слишком высокий "use client"
- 2. Тяжелые эффекты на старте
- 3. Повторный fetch тех же данных
- 4. Неправильные ключи и нестабильное дерево
- Разбор производительности: что именно измерять
- Практики, которые обычно работают лучше
- Архитектурные практики
- Практики кода
- Практики наблюдаемости
- Rollout и rollback
- Частые ошибки
- Как отвечать на интервью
- FAQ
- Что такое hydration в React простыми словами?
- Почему возникают hydration mismatch?
- SSR всегда ускоряет страницу?
- Чем selective hydration отличается от обычной?
- Как уменьшить стоимость hydration?
- Итоги
Введение
Когда говорят про SSR в React, разговор часто обрывается на фразе «сервер отдал HTML быстрее». Это только половина картины. После того как браузер получил HTML, начинается этап, который и определяет, когда страница станет по-настоящему живой. Этот этап называется hydration.
Если коротко, hydration в React после SSR означает, что клиентский React не рисует экран с нуля, а пытается взять уже существующую HTML-разметку, сопоставить ее со своим деревом компонентов и подключить интерактивность. Именно здесь появляются типичные production-проблемы: hydration mismatch, длинные блокировки main thread, мигание UI, повторные запросы и ощущение «контент виден, но страница еще не работает».
Тема всплывает и в архитектурных обсуждениях, и на собеседованиях middle/senior уровня. Особенно сейчас, когда рядом существуют SSR, RSC, Suspense и concurrent-механика React. Для общего контекста полезно держать рядом разбор SSR vs CSR vs RSC: он помогает не путать доставку HTML, серверные компоненты и сам процесс гидрации.
Больше вопросов в Telegram
Ежедневные разборы и реальные кейсы с интервью.
Что такое hydration без упрощений
Hydration - это не «еще один render». Сервер уже выполнил render и отдал HTML. Теперь задача React в браузере другая:
- разобрать клиентский bundle;
- построить клиентское дерево React для текущего экрана;
- сопоставить это дерево с уже существующим DOM;
- привязать обработчики событий;
- подготовить дерево к дальнейшим обновлениям без полной пересборки страницы.
Важный нюанс: браузер может показать HTML до завершения hydration. Поэтому у SSR-страницы есть как минимум два разных момента готовности:
- контент виден;
- интерфейс интерактивен.
Именно путаница между этими двумя состояниями рождает много слабых выводов вроде «SSR всегда быстрый». Нет. Он может дать ранний первый экран, но если гидрация тяжелая, пользователь увидит кнопку раньше, чем сможет по ней нажать.
Что происходит после SSR по шагам
1. Браузер получает HTML и начинает парсинг
На первом этапе пользователь видит серверный HTML. Для контентных страниц это уже большой выигрыш: текст, заголовки, изображения и базовая структура появляются раньше, чем при чистом CSR. Но этот HTML пока пассивен.
2. Загружается клиентский JavaScript
Дальше браузер скачивает, парсит и исполняет клиентские чанки. На слабом устройстве именно здесь часто лежит узкое место. Если bundle раздут, выигрыш от SSR частично съедается parse/execute cost. По этой причине разговор о гидрации почти всегда связан с размером клиентской сборки и границами "use client" в server-first архитектуре. Эта связка подробно разобрана в статье про Server Components.
3. React строит клиентское дерево и пытается его привязать к DOM
React вызывает hydrateRoot(...), получает корневой контейнер и начинает сравнивать ожидаемую структуру со страницей, которую уже отрисовал сервер.
import { hydrateRoot } from "react-dom/client";
import { App } from "./App";
hydrateRoot(document.getElementById("root")!, <App />);
Смысл здесь не в том, чтобы заново создать весь DOM, а в том, чтобы переиспользовать уже существующие узлы, если серверный и клиентский вывод совпадают.
4. Подключаются события и состояние
После успешного сопоставления React начинает «оживлять» интерфейс: клики, ввод, локальное состояние, эффекты, подписки. В этот момент страница переходит из режима «статичная, но видимая» в режим «интерактивная и управляемая React».
5. Дерево становится обычным клиентским React-деревом
После завершения hydration следующие обновления уже идут как обычные клиентские обновления. SSR больше не участвует в локальной жизни этого экземпляра страницы, пока не случится новая навигация или новый серверный ответ.
Архитектурный разбор: где именно находится гидрация в общем pipeline
Для production удобнее думать о странице как о цепочке из пяти стадий:
| Стадия | Где выполняется | Что получает пользователь | Главное узкое место |
|---|---|---|---|
| Data fetching для SSR | сервер | пока ничего | latency до API и базы |
| Server render | сервер | пока ничего | CPU сервера и шаблонный render |
| HTML response | сеть + браузер | видимый контент | TTFB и размер ответа |
| Hydration | браузер | переход к интерактивности | parse/execute JS и main thread |
| Последующие updates | браузер | обычный React UI | локальные ререндеры и эффекты |
Практический вывод простой: SSR отвечает за ранний HTML, а hydration отвечает за переход к интерактивности. Это разные точки оптимизации и разные точки отказа.
На интервью сильный ответ обычно звучит так: «Я отдельно оцениваю стоимость серверного рендера и стоимость hydration. Быстрый первый HTML не означает быстрый usable UI». Именно эта формулировка обычно отделяет инженерный ответ от пересказа документации.
Базовый пример: сервер и клиент совпадают
Если сервер и клиент строят один и тот же JSX из одних и тех же данных, hydration проходит спокойно.
type ProductProps = {
title: string;
price: number;
};
export function ProductCard({ title, price }: ProductProps) {
return (
<article>
<h2>{title}</h2>
<p>{price} ₽</p>
<button>Купить</button>
</article>
);
}
Сервер отдаст HTML карточки, а клиент потом подключит обработчик кнопки, если он есть в реальном дереве. Здесь важен не сам JSX, а детерминированность вывода: одинаковые входные данные дают одинаковую разметку на сервере и клиенте.
Почему возникает hydration mismatch
Hydration mismatch появляется, когда React ожидает один DOM, а в контейнере уже лежит другой. Типовые причины почти всегда повторяются.
1. Нестабильные значения в рендере
Самый частый антипример: использовать Date.now(), Math.random() или генерацию id прямо в JSX во время первого рендера.
export function BadClock() {
return <time>{Date.now()}</time>;
}
На сервере и клиенте значения почти гарантированно будут разными. Итог: warning, повторная пересборка участка, иногда визуальный дерг.
2. Условный JSX по browser API
export function BadThemeLabel() {
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
return <span>{isDark ? "dark" : "light"}</span>;
}
Такой код вообще не должен участвовать в server render. На сервере window нет, а если обойти это условием, можно получить разный первичный вывод на клиенте и сервере.
3. Разные данные на сервере и клиенте
Если сервер отрендерил список из одного набора данных, а клиент сразу после загрузки сделал re-fetch и получил другой результат, интерфейс может мигнуть или пересобраться прямо в hydration-фазе.
4. Некорректная работа с формами
Формы особенно чувствительны к расхождению между defaultValue и value. Отдельно это разобрано в статье про формы, SSR и hydration.
Как исправлять mismatch без костылей
Рабочее правило: все, что влияет на первый render, должно быть детерминированным и одинаковым на сервере и клиенте.
Плохой вариант:
export function PromoBanner() {
const variant = Math.random() > 0.5 ? "A" : "B";
return <section>{variant}</section>;
}
Лучше так:
type PromoBannerProps = {
variant: "A" | "B";
};
export function PromoBanner({ variant }: PromoBannerProps) {
return <section>{variant}</section>;
}
Теперь решение о варианте принимается вне рендера: на сервере, в data-layer или через заранее переданный проп. Это важно не только для корректности, но и для отладки. Когда variant вычисляется детерминированно, команда видит источник истины, а не случайное поведение в браузере.
Selective hydration: почему React не обязан оживлять все строго по очереди
В React 18+ гидрация стала гибче. Если раньше тему объясняли как «весь SSR-экран постепенно становится интерактивным», то на практике React умеет приоритизировать участки дерева.
Смысл selective hydration такой:
- если пользователь уже взаимодействует с конкретным участком;
- если рядом есть границы
Suspense; - если часть дерева готова раньше другой;
React может активировать более важные зоны раньше, не дожидаясь полной гидрации всего экрана.
Это особенно полезно для длинных страниц, где header, навигация и поиск важнее, чем тяжелый вторичный виджет ниже по странице. Здесь hydration тесно связана с тем, как вы расставляете Suspense-границы и как делите экран на интерактивные острова. Детально этот механизм дополняет разбор Suspense в React и статья про Concurrent Rendering.
Сравнение стратегий: CSR, SSR + hydration, SSR + узкие client islands
| Критерий | CSR | SSR + hydration всего дерева | SSR + узкие client islands |
|---|---|---|---|
| Первый HTML | поздний | ранний | ранний |
| Стоимость JS на клиенте | высокая | средняя или высокая | ниже |
| Цена гидрации | нет отдельной, но есть полный client render | часто заметная | ниже при узких границах |
| Риск mismatch | низкий | средний | средний, но зона меньше |
| SEO | слабее | хороший | хороший |
| Подходит для | закрытых интерактивных приложений | публичных страниц со средним UI | контентных страниц и server-first экранов |
Ключевая мысль по таблице: проблема обычно не в самом SSR, а в том, что команда после SSR продолжает отправлять слишком много клиентского кода и гидратирует лишнее дерево.
Production pitfalls: где гидрация чаще всего становится дорогой
1. Слишком высокий "use client"
Если клиентской границей помечают layout или крупный route-level контейнер без необходимости, в браузер уезжает слишком большой кусок дерева. В результате HTML пришел рано, но потом вся страница долго «оживает».
2. Тяжелые эффекты на старте
Даже если hydration формально завершилась без mismatch, ранние useEffect могут сразу занять main thread: аналитика, listeners, re-measure layout, повторные fetch, синхронные вычисления.
3. Повторный fetch тех же данных
Если сервер уже потратил ресурсы на данные для SSR, а клиент при mount тут же запрашивает их заново без причины, команда платит дважды: сервером и стартовой клиентской работой.
4. Неправильные ключи и нестабильное дерево
Если структура списка на сервере и клиенте не совпадает по key, React получает лишнюю сложность прямо в момент hydration. Базовая механика идентичности элементов подробно разбирается в материале про ошибки с key.
Разбор производительности: что именно измерять
Когда обсуждают hydration, полезно отделять три разных вопроса:
- Насколько быстро пришел HTML?
- Насколько быстро JavaScript стал готов к работе?
- Когда пользователь реально может взаимодействовать с экраном без лагов?
Минимальный набор метрик:
TTFBдля серверного ответа;LCPдля появления основного контента;- размер клиентского bundle и стоимость parse/execute;
- long tasks на main thread в момент старта;
- задержка до первой осмысленной интерактивности.
Если после SSR у вас хороший LCP, но кнопки и фильтры несколько секунд не отвечают, проблема почти наверняка уже не в серверном рендере, а в гидрации и стартовом клиентском коде.
Практики, которые обычно работают лучше
Архитектурные практики
- Проектируйте экран по принципу
server first, а неclient by default. - Держите read-only блоки на серверной стороне, если им не нужны события и локальное состояние.
- Делайте интерактивные острова узкими: форма поиска, фильтр, кнопка лайка, чат-виджет, но не весь экран целиком.
Практики кода
- Не вычисляйте нестабильные значения в первом рендере.
- Не обращайтесь к browser API внутри server render-пути.
- Не запускайте лишний re-fetch сразу после hydration без проверяемой причины.
Практики наблюдаемости
- Логируйте hydration warnings в staging и не игнорируйте их как «шум».
- Снимайте профили на слабых устройствах, а не только на машине разработчика.
- Разделяйте в метриках серверную latency и клиентскую стоимость старта.
Rollout и rollback
- Миграцию на server-first схему делайте поэтапно, а не одним релизом.
- Держите feature flag на спорных client boundaries.
- Сравнивайте размер bundle и время интерактивности до и после изменений, а не только субъективное ощущение команды.
Прокачай React за 7 дней
20 вопросов и разборов по React Hooks.
Частые ошибки
- Считать, что ранний HTML автоматически означает быстрый интерфейс.
- Путать
SSRиhydration, как будто это один и тот же этап. - Лечить mismatch через
suppressHydrationWarning, не исправив первопричину. - Дублировать data fetching на сервере и клиенте без явной стратегии.
- Поднимать
"use client"слишком высоко и потом удивляться тяжелой гидрации. - Оценивать только
LCP, игнорируя лаги после появления контента.
Как отвечать на интервью
Рабочая структура ответа на вопрос «что происходит после SSR в React?» выглядит так:
- Сервер отдает HTML, и пользователь может увидеть контент раньше.
- Затем браузер загружает клиентский JavaScript.
- React запускает hydration: сопоставляет клиентское дерево с готовым DOM и подключает обработчики событий.
- Если серверный и клиентский вывод расходятся, возникает hydration mismatch.
- Главное ограничение SSR не в HTML, а в цене последующей гидрации и клиентского bundle.
Пример сильного ответа:
После SSR браузер уже может показать HTML, но страница еще не интерактивна. React загружает клиентский код, вызывает hydration, сопоставляет свое дерево с существующим DOM и подключает события. Если серверный и клиентский render отличаются, возникают hydration mismatch и лишняя пересборка. Поэтому я оцениваю не только TTFB и LCP, но и стоимость hydration, размер client bundle и границы client components.
Такой ответ показывает механизм, точку отказа и инженерные компромиссы. Слабый ответ обычно звучит короче: «после SSR React просто подключает события». Формально не ложь, но глубины в нем почти нет.
Практика React-собеседований по рендерингу и архитектуре
Разберите реальные вопросы по SSR, hydration, RSC, производительности и границам client/server в формате mock interview и коротких инженерных разборов.
FAQ
Что такое hydration в React простыми словами?
Это этап после SSR, когда React в браузере берет готовый HTML, сверяет его со своим деревом и делает страницу интерактивной: подключает события, состояние и дальнейшие обновления.
Почему возникают hydration mismatch?
Почти всегда из-за расхождения между серверным и клиентским выводом: случайные значения, даты, чтение window в рендере, разные данные или нестабильная структура JSX.
SSR всегда ускоряет страницу?
Нет. SSR ускоряет появление первого HTML, но не гарантирует быструю интерактивность. Если bundle большой и гидрация дорогая, интерфейс может быть видимым, но еще долго оставаться «полуживым».
Чем selective hydration отличается от обычной?
React может не ждать полной активации всего дерева, а гидратировать более важные участки раньше, особенно если пользователь уже с ними взаимодействует или если дерево разбито на границы Suspense.
Как уменьшить стоимость hydration?
Сужать клиентские границы, не отправлять лишний JavaScript, переносить read-only блоки в серверный слой, избегать тяжелых клиентских эффектов на старте и следить за повторным fetch после SSR.
Итоги
Hydration в React - это не второстепенная деталь после SSR, а критическая часть pipeline. Сервер дает ранний HTML, но именно гидрация решает, когда страница станет управляемой и отзывчивой.
Если смотреть на тему инженерно, главный вопрос не «есть ли у нас SSR», а «сколько клиентской работы остается после SSR и насколько она дорога». От этого зависят и реальная производительность, и архитектурные решения вокруг RSC, Suspense, client islands и размера bundle.
Больше вопросов в Telegram
Ежедневные разборы и реальные кейсы с интервью.
Автор
Lexicon Team
Читайте также
frontend
React SSR vs CSR vs RSC: что выбрать и как объяснить разницу на интервью
Подробно разбираем React SSR vs CSR vs RSC: архитектура, производительность, компромиссы, примеры на Next.js и типичные ошибки в production.
frontend
Server Components в React: как работают и зачем нужны
Подробно разбираем React Server Components: архитектура Flight, server components vs client, ограничения, App Router в Next.js и вопросы с собеседований 2026.
frontend
Webpack vs Vite для React: что выбрать в 2026 году и как объяснить выбор на интервью
Сравниваем Webpack и Vite для React: dev server, HMR, production build, экосистема, производительность, типичные ошибки и сильный ответ для собеседования.