Навигация

Гидратация: SSR, mismatch и Streaming в React

Гидратация — мост между серверным HTML и интерактивным React-приложением. Сервер отдаёт готовый HTML, браузер загружает JS-бандл, и React «навешивает» обработчики на существующие DOM-узлы, не пересоздавая их. Без понимания этого процесса сложно избежать hydration mismatch и оптимизировать TTI.

Содержание

  1. Что такое гидратация
  2. Как работает гидратация
  3. Почему гидратация важна
  4. Проблемы классической гидратации
  5. React 18: Streaming SSR и Selective Hydration
  6. React 19 и Partial Pre-rendering
  7. Способы уменьшить влияние гидратации
  8. Когда гидратация не нужна
  9. Ключевые тезисы для интервью
  10. Заключение

Что такое гидратация

Гидратация (hydration) — это процесс подключения JavaScript к уже отрендеренному статическому HTML на клиенте, чтобы сделать страницу интерактивной. Сервер отдаёт полный HTML, браузер загружает JS-бандл React, и React «навешивает» на существующие DOM-узлы обработчики событий, состояние и эффекты.

Без гидратации страница выглядела бы как статичный манекен: виден, но не реагирует.

Как работает гидратация

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│   Сервер    │ ───► │  Браузер    │ ───► │   React     │
│  отдаёт HTML│      │  получает   │      │  гидратирует│
│  + данные   │      │  HTML + JS  │      │  DOM        │
└─────────────┘      └─────────────┘      └─────────────┘

Шаг 1: Серверный рендеринг

Сервер генерирует полный HTML страницы и отправляет его браузеру. Пользователь сразу видит контент, но страница ещё не интерактивна.

Шаг 2: Загрузка JavaScript

Браузер загружает JavaScript-бандл React и зависимости. В это время страница отображается, но кнопки и формы не работают.

Шаг 3: Подключение React

React строит виртуальное DOM-дерево, сравнивает его с существующим HTML и прикрепляет обработчики событий, состояния и эффекты к DOM-узлам. Важно: React не пересоздаёт DOM, а использует существующий.

Шаг 4: Интерактивность

После гидратации страница становится полностью интерактивной. Время от первой отрисовки до гидратации влияет на метрику TTI (Time to Interactive).

Почему гидратация важна

  • Быстрая первая отрисовка — пользователь видит контент мгновенно, не дожидаясь загрузки JS.
  • SEO — поисковые боты видят полный HTML.
  • Интерактивность — после загрузки JS страница полностью функциональна.
  • Производительность восприятия — даже при медленном соединении кажется, что сайт работает быстро.

Проблемы классической гидратации

Большой JS-бандл

Чем больше кода, тем дольше загрузка и выполнение. Это увеличивает TBT (Total Blocking Time) и задерживает интерактивность.

Hydration Mismatch

Ошибка возникает, когда HTML от сервера отличается от того, что React ожидает на клиенте. React не может корректно «подключиться» к DOM и перерендеривает страницу целиком, теряя преимущества SSR.

Частые причины

  • Использование Date.now(), Math.random() — разные значения на сервере и клиенте.
  • Обращение к window, document, localStorage — недоступны на сервере.
  • Условный рендеринг на основе браузерных API.
  • Разница в форматировании дат/чисел из-за локали.

Как избежать

  • Используйте useEffect для кода, зависящего от браузера.
  • Убедитесь, что рендеринг детерминирован (одинаковый результат при одинаковых входных данных).
  • Используйте suppressHydrationWarning только для безобидных различий (например, временные метки).
  • Применяйте typeof window !== 'undefined' только внутри эффектов, а не в рендере.
// ❌ Плохо: разный HTML на сервере и клиенте
function Page() {
  const width = window.innerWidth; // window недоступен на сервере
  return <div>{width}</div>;
}

// ✅ Хорошо: браузерный код в useEffect
function Page() {
  const [width, setWidth] = useState(0);

  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);

  return <div>{width}</div>;
}

Задержка до интерактивности

Весь HTML должен быть гидратирован, прежде чем страница станет интерактивной. На больших страницах это может занять секунды.

React 18: Streaming SSR и Selective Hydration

React 18 изменил подход к гидратации:

  • Streaming SSR — HTML отправляется частями (чанками) по мере готовности компонентов.
  • Selective Hydration — React гидратирует компоненты по приоритету: сначала тот, с которым взаимодействует пользователь.
// Серверный компонент потоково отдаёт HTML
import { Suspense } from 'react';

export default function Page() {
  return (
    <>
      <Header />
      <Suspense fallback={<Skeleton />}>
        <Comments />
      </Suspense>
    </>
  );
}

React 19 и Partial Pre-rendering

React 19 и Next.js 14+ развивают концепцию Partial Pre-rendering (PPR):

  • Статическая оболочка страницы генерируется заранее.
  • Динамические части подгружаются и гидратируются по мере необходимости.
  • Уменьшается объём JS, необходимый для первой интерактивности.

Способы уменьшить влияние гидратации

  1. Меньше клиентского кода — выносите логику в серверные компоненты.
  2. Lazy loading — грузите тяжёлые компоненты динамически.
  3. Разделение на чанки — code splitting по маршрутам.
  4. Избегайте hydration mismatch — детерминированный рендеринг.
  5. Используйте React.Suspense — для потоковой подгрузки частей страницы.
  6. Рассмотрите Partial Hydration — в Next.js через Server Components.

Когда гидратация не нужна

  • Чистый SSG без интерактива — например, статический блог, где не нужны хуки состояния.
  • Полностью серверные компоненты — они не отправляют JS на клиент и не гидратируются.

Ключевые тезисы для интервью

  • Гидратация — подключение React к серверному HTML без пересоздания DOM: сервер отдаёт HTML, браузер загружает JS, React reconciliates DOM и навешивает обработчики событий.
  • Hydration Mismatch возникает, когда серверный и клиентский HTML отличаются (Date.now(), window, Math.random()). Код, зависящий от браузерных API, нужно изолировать в useEffect.
  • Большой JS-бандл увеличивает TBT и задерживает TTI — весь HTML ожидает полной загрузки и гидратации, прежде чем страница станет интерактивной.
  • Streaming SSR (React 18) отправляет HTML чанками по мере готовности компонентов через Suspense; Selective Hydration гидратирует по приоритету — сначала компонент, с которым взаимодействует пользователь.
  • PPR (Partial Pre-rendering): статическая оболочка генерируется при сборке, динамические части — при запросе через Suspense. Это объединяет преимущества SSG и SSR.
  • Серверные компоненты не гидратируются — они не отправляют JS на клиент, что радикально уменьшает размер бандла.

Заключение

Гидратация даёт быструю первую отрисовку и SEO, но добавляет накладные расходы — JS-бандл и потенциальные hydration mismatch. Современные подходы (Streaming SSR, Selective Hydration, PPR) делают гидратацию постепенной и приоритизированной: пользователь видит контент раньше, а интерактивность появляется там, где она нужна первой.