Angular

SSR и гидрация

Чистое клиентское приложение отдаёт пустой div и заставляет браузер скачать, распарсить и выполнить весь JavaScript, прежде чем покажет хоть что-то. Поисковые роботы и медленные устройства страдают. SSR рендерит готовый HTML на сервере: пользователь видит контент сразу. Гидрация - это аккуратная стыковка: клиентский Angular переиспользует уже существующий серверный DOM вместо того, чтобы стереть его и отрисовать заново.

  • E-commerce и медиа: SSR ради SEO и быстрого LCP на товарных и контентных страницах
  • Лендинги: мгновенный показ контента до загрузки фреймворка
  • Медленные сети и устройства: видимый контент пока скачивается JS
  • Соцсети: корректные og-теги и превью при шаринге ссылок
  • Гидрация без мерцания: серверный DOM переиспользуется, экран не перерисовывается с нуля

Предварительные знания

  • Роутинг Angular: как маршруты сопоставляются компонентам
  • Различие между рендером на сервере и в браузере
  • Базовое понимание жизненного цикла компонента и DOM

От стирания DOM к его переиспользованию

Ранний серверный рендеринг в Angular (Angular Universal) умел отдавать HTML, но при загрузке клиента стирал серверный DOM и отрисовывал всё заново. Это вызывало мерцание и теряло смысл серверного рендера. Angular 16 (2023) принёс полную гидрацию без разрушения: клиент переиспользует серверный DOM. Angular 17 интегрировал SSR в стандартный ng new. Angular 18 (2024) добавил воспроизведение событий, а Angular 19-21 - инкрементальную гидрацию. Гидрация стала включаться по умолчанию в SSR-приложениях.

Настройка SSR и гидрации

Создать SSR-приложение можно командой ng new --ssr или добавить SSR в существующее через ng add @angular/ssr. Angular настроит серверный entry-point и сборку. Гидрация на клиенте включается одним провайдером в конфигурации приложения.

Без provideClientHydration клиент при загрузке стёр бы серверный DOM и отрисовал всё заново, вызвав мерцание. С этим провайдером Angular обходит серверный DOM, сопоставляет его со структурой компонентов и присоединяет к нему обработчики, не пересоздавая элементы.

Гидрация в Angular - non-destructive: серверный HTML остаётся на месте, а клиент лишь оживляет его. Это убирает мерцание (FOUC) и сохраняет уже отрисованный браузером результат, включая позицию прокрутки и состояние полей ввода.

Что произойдёт с серверным DOM, если SSR настроен, но provideClientHydration не добавлен?

Воспроизведение событий

Между показом серверного HTML и завершением гидрации есть окно, когда контент виден, но обработчики ещё не присоединены. Клик в это окно при обычной гидрации потерялся бы. Воспроизведение событий (event replay) решает это: ранние события записываются и проигрываются после гидрации.

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

Воспроизведение событий особенно важно для INP в Core Web Vitals: оно убирает класс потерянных взаимодействий в первые секунды после загрузки, когда пользователь видит готовую страницу, но приложение ещё гидрируется.

Какую проблему решает withEventReplay()?

Рассинхрон гидрации и как его избежать

Гидрация требует, чтобы DOM, отрисованный на сервере, совпадал с тем, что ожидает клиент. Если структуры расходятся, возникает hydration mismatch: Angular не может сопоставить узлы и в этом поддереве откатывается к разрушительной перерисовке, теряя выгоду и иногда показывая ошибку в консоли.

Источник рассинхронаПочему ломаетРешение
Прямые манипуляции DOM через innerHTMLКлиент не знает об изменениях вне AngularМенять DOM только через шаблон и привязки
Обращение к window или document на сервереНа сервере их нет, рендер расходитсяПроверять платформу через isPlatformBrowser или afterNextRender
Недетерминированный рендер (Math.random, Date.now)Сервер и клиент дают разный результатФиксировать значение или вычислять после гидрации
Невалидный HTML (например, div внутри p)Браузер чинит разметку, структура расходитсяСоблюдать корректную вложенность тегов

afterNextRender выполняется только в браузере и только после гидрации, поэтому внутри него безопасно обращаться к window и измерять DOM. Чтение window прямо в конструкторе или в ngOnInit упадёт на сервере или вызовет рассинхрон.

Почему чтение window.innerWidth прямо в конструкторе компонента ломает гидрацию?

Связь с другими темами

SSR опирается на роутер и ведёт к инкрементальной гидрации:

  • Роутинг — Сервер рендерит компонент маршрута по URL запроса
  • Инкрементальная гидрация — Следующий шаг: гидрировать не всё дерево сразу, а части по мере надобности

Итог

  • SSR рендерит HTML на сервере, чтобы пользователь и поисковые роботы видели контент до загрузки JS
  • Гидрация переиспользует серверный DOM на клиенте без его стирания и повторной отрисовки
  • provideClientHydration() включает гидрацию, withEventReplay() добавляет воспроизведение ранних событий
  • Рассинхрон сервера и клиента ломает гидрацию: DOM должен быть одинаковым на обеих сторонах
  • Источники рассинхрона: прямые манипуляции DOM, обращение к window на сервере, недетерминированный рендер

Связанные уроки

  • ng-22-router-intro — SSR рендерит маршруты на сервере, поэтому работу роутера нужно понимать заранее
  • ng-35-incremental-hydration — Инкрементальная гидрация - развитие полной гидрации, разбираемой здесь
SSR и гидрация

0

1

Войти