Перейти к содержанию

Гидратация

📅 30.09.2026

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

Гидратация восстанавливает на клиенте приложение, которое уже отрисовал сервер. Сюда входит переиспользование серверных структур DOM, сохранение состояния приложения, передача данных, которые сервер уже получил, и другие процессы.

Зачем нужна гидратация

Гидратация ускоряет приложение: не нужно заново создавать узлы DOM. Angular сопоставляет уже существующие элементы DOM со структурой приложения во время выполнения и по возможности переиспользует узлы. Выигрыш измеряют статистикой Core Web Vitals (CWV): меньше задержка первого ввода (FID), отрисовка самого крупного содержимого (LCP) и совокупное смещение макета (CLS). Эти цифры влияют и на SEO.

Без гидратации приложение Angular после серверного рендеринга уничтожает DOM и рисует его заново, и интерфейс может заметно мигнуть. Повторная отрисовка бьёт по Core Web Vitals вроде LCP и вызывает смещение макета. С гидратацией готовый DOM переиспользуется, и мигания нет.

Как включить гидратацию в Angular

Гидратация доступна только приложениям с серверным рендерингом (SSR). Сначала включите серверный рендеринг по руководству Angular по SSR.

Через Angular CLI

Если SSR включали через Angular CLI — при создании приложения или позже командой ng add @angular/ssr, — код гидратации уже должен быть в приложении.

Ручная настройка

В своей сборке, где SSR включали не через Angular CLI, гидратацию добавляют вручную. В корневом компоненте или модуле импортируют provideClientHydration из @angular/platform-browser и кладут этот провайдер в список провайдеров старта приложения.

1
2
3
4
5
6
7
8
9
import {
  bootstrapApplication,
  provideClientHydration,
} from '@angular/platform-browser';
...

bootstrapApplication(App, {
  providers: [provideClientHydration()]
});

Если приложение на NgModule, provideClientHydration добавляют в список провайдеров корневого модуля.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import {provideClientHydration} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

@NgModule({
  declarations: [App],
  exports: [App],
  bootstrap: [App],
  providers: [provideClientHydration()],
})
export class AppModule {}

Вызов provideClientHydration() должен входить и в набор провайдеров, которым приложение стартует на сервере. В проекте со структурой по умолчанию (команда ng new) достаточно добавить вызов в корневой AppModule: этот модуль импортирует серверный модуль. В своей сборке добавьте provideClientHydration() в список провайдеров серверного старта.

Проверка, что гидратация включена

После настройки гидратации и запуска сервера откройте приложение в браузере.

До полной гидратации, скорее всего, придётся поправить прямую работу с DOM: перейти на конструкции Angular или поставить ngSkipHydration. Подробнее — в разделах Ограничения, Прямая работа с DOM и Как пропустить гидратацию отдельных компонентов.

В режиме разработки гидратацию подтверждают в консоли инструментов разработчика браузера. Там будет сообщение со статистикой гидратации, например числом компонентов и узлов. Angular считает статистику по всем компонентам страницы, включая компоненты сторонних библиотек.

Статус гидратации компонентов на странице показывает и расширение Angular DevTools. В DevTools можно включить оверлей: он отмечает, какие части страницы гидратированы. Если есть ошибка расхождения гидратации, DevTools подсветит компонент, который её вызвал.

Захват и повтор событий

Когда приложение отрисовано на сервере, оно видно в браузере сразу после загрузки HTML. Пользователь может решить, что со страницей уже можно работать, но слушатели событий появятся только после гидратации. Начиная с v18, повтор событий (Event Replay) захватывает все события до гидратации и проигрывает их, когда гидратация закончилась. Включают его функцией withEventReplay():

1
2
3
4
5
import {provideClientHydration, withEventReplay} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration(withEventReplay())],
});

Как работает повтор событий

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

Повтор событий делится на три фазы:

  • Захват действий пользователя. До гидратации Event Replay перехватывает и сохраняет все взаимодействия: клики и другие нативные события браузера.
  • Хранение событий. Контракт событий (Event Contract) держит в памяти всё, что записано на предыдущем шаге, чтобы потом это можно было проиграть.
  • Повторный запуск событий. Когда гидратация завершена, Angular заново вызывает захваченные события.

Повтор событий поддерживает нативные события браузера, например click, mouseover и focusin. Библиотека JSAction, на которой построен повтор, описана в readme.

Так действия пользователя до гидратации не пропадают, и опыт остаётся цельным.

Если включена инкрементальная гидратация, повтор событий включается автоматически.

Ограничения

С гидратацией у приложения появляются ограничения, которых без неё нет. Сгенерированная структура DOM на сервере и на клиенте должна совпадать. Гидратация ждёт одинаковое дерево DOM в обоих местах. Сюда входят пробелы и узлы-комментарии, которые Angular создаёт при отрисовке на сервере. Эти пробелы и узлы должны быть в HTML, который отдал серверный рендеринг.

HTML, который получился при серверном рендеринге, нельзя менять между сервером и клиентом.

Если деревья DOM сервера и клиента расходятся, гидратация не сможет сопоставить ожидаемое с тем, что реально есть в DOM. Чаще всего виноваты компоненты, которые меняют DOM напрямую через нативные API.

Прямая работа с DOM

Если компонент меняет DOM нативными API или через innerHTML и outerHTML, гидратация завершится ошибкой. Типичные случаи: обращение к document, поиск конкретных элементов и вставка дополнительных узлов через appendChild. Отсоединение узлов и перенос их в другое место тоже даёт ошибку.

Angular не знает об этих изменениях DOM и не может их разрешить во время гидратации. Он ждёт одну структуру, а встречает другую. Расхождение срывает гидратацию и выбрасывает ошибку несовпадения DOM (см. ниже).

Такую работу с DOM лучше убрать из компонента и сделать через API Angular. Если переписать поведение пока нельзя, поставьте атрибут ngSkipHydration (описан ниже), пока не появится решение, совместимое с гидратацией.

Корректная структура HTML

Если в шаблоне компонента некорректная структура HTML, при гидратации возможна ошибка несовпадения DOM.

Самые частые случаи:

  • <table> без <tbody>
  • <div> внутри <p>
  • <a> внутри другого <a>

Если неясно, валиден ли HTML, проверьте его валидатором синтаксиса.

Стандарт HTML не требует элемент <tbody> внутри таблиц, но современные браузеры сами создают <tbody> в таблицах, где его не объявили. Из-за этого расхождения всегда явно объявляйте <tbody> в таблицах, иначе гидратация упадёт с ошибкой.

Настройка сохранения пробелов

С гидратацией лучше оставлять значение по умолчанию false для preserveWhitespaces. Если параметра нет в tsconfig, значение и так false, ничего менять не нужно. Если включить сохранение пробелов через preserveWhitespaces: true в tsconfig, с гидратацией возможны проблемы. Эта конфигурация пока поддерживается не полностью.

Значение должно быть одинаковым в tsconfig.server.json для сервера и в tsconfig.app.json для браузерной сборки. Разные значения ломают гидратацию.

Если параметр всё же задают в tsconfig, его лучше поставить только в tsconfig.app.json: tsconfig.server.json по умолчанию наследует его оттуда.

Свои реализации Zone.js и Noop пока не поддерживаются

Гидратация опирается на сигнал Zone.js о том, что приложение стало стабильным. По этому сигналу Angular на сервере начинает сериализацию, а на клиенте — уборку после гидратации: удаляет узлы DOM, которые так и не были сопоставлены.

Своя реализация Zone.js или реализация «noop» может сдвинуть момент события «stable», и сериализация или уборка начнутся слишком рано или слишком поздно. Эта конфигурация пока поддерживается не полностью: момент события onStable в своей реализации Zone.js, возможно, придётся подстроить.

Ошибки

Ошибки гидратации бывают разными: от несовпадения узлов до случая, когда ngSkipHydration стоит на недопустимом узле-хосте. Чаще всего ошибка из-за прямой работы с DOM через нативные API: на клиенте гидратация не находит дерево DOM, которое отрисовал сервер. Другой такой случай описан выше в разделе Корректная структура HTML. Следите, чтобы HTML в шаблонах был корректным, и этот случай не возникнет.

Полный список ошибок гидратации — в справочнике ошибок.

Как пропустить гидратацию отдельных компонентов

Отдельные компоненты с гидратацией работают плохо из-за проблем вроде прямой работы с DOM. Обходной путь — атрибут ngSkipHydration на теге компонента: гидратация всего компонента пропускается.

1
<app-example ngSkipHydration />

Тот же ngSkipHydration можно задать привязкой хоста.

1
2
3
4
5
@Component({
  ...
  host: {ngSkipHydration: 'true'},
})
class ExampleComponent {}

Атрибут ngSkipHydration заставляет Angular пропустить гидратацию всего компонента и его потомков. Компонент ведёт себя так, будто гидратации нет: уничтожает себя и рисует заново.

Так чинятся проблемы отрисовки, но у этого компонента и его потомков не будет выгод гидратации. Чтобы убрать пропуск, реализацию нужно поправить и уйти от приёмов, которые ломают гидратацию (то есть от прямой работы с DOM).

Атрибут ngSkipHydration допустим только на узле-хосте компонента. На других узлах Angular выбросит ошибку.

Если поставить ngSkipHydration на корневой компонент приложения, гидратация фактически выключится для всего приложения. Атрибут — крайняя мера. Компоненты, которые ломают гидратацию, стоит считать ошибками и исправлять.

Момент гидратации и стабильность приложения

Стабильность приложения — важная часть гидратации. Гидратация и всё, что идёт после неё, начинаются только когда приложение сообщило о стабильности. Стабильность задерживают по-разному: таймауты и интервалы, неразрешённые промисы, незавершённые микрозадачи. Тогда возможна ошибка приложение остаётся нестабильным: за 10 секунд приложение так и не стало стабильным. Если гидратация не начинается сразу, посмотрите, что держит стабильность, и уберите эти задержки.

Отладка стабильности приложения

Утилита provideStabilityDebugging показывает, почему приложение не стабилизируется. В режиме разработки она уже есть, если используется provideClientHydration. Её можно добавить в провайдеры вручную — для продакшен-сборок или для SSR без гидратации. Если стабилизация занимает больше ожидаемого, утилита пишет сведения в консоль.

1
2
3
4
5
6
7
import {provideStabilityDebugging} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import 'zone.js/plugins/task-tracking'; // Use if you have Zone.js with `provideZoneChangeDetection`

bootstrapApplication(App, {
  providers: [provideStabilityDebugging()],
});

Когда утилита включена, она пишет в консоль незавершённые задачи (PendingTasks). Если приложение на Zone.js, импорт zone.js/plugins/task-tracking показывает, какие макрозадачи не дают зоне Angular стабилизироваться. Плагин даёт стек создания макрозадачи и помогает найти источник задержки.

Angular не вырезает плагин отслеживания задач zone.js и эту утилиту из продакшен-сборок. Пользуйтесь ими только для временной отладки стабильности во время разработки, в том числе на оптимизированных продакшен-сборках.

Интернационализация

По умолчанию Angular пропускает гидратацию компонентов с блоками i18n и рисует такие компоненты заново.

Чтобы включить гидратацию блоков i18n, добавьте withI18nSupport в вызов provideClientHydration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import {
  bootstrapApplication,
  provideClientHydration,
  withI18nSupport,
} from '@angular/platform-browser';
...

bootstrapApplication(App, {
  providers: [provideClientHydration(withI18nSupport())]
});

Одинаковая отрисовка на сервере и на клиенте

Не ставьте блоки @if и другие условия, которые на сервере и на клиенте показывают разное содержимое, например @if с функцией Angular isPlatformBrowser. Разная отрисовка сдвигает макет, портит опыт и Core Web Vitals.

Сторонние библиотеки с работой с DOM

Ряд сторонних библиотек рисует себя через прямую работу с DOM. Наглядный пример — графики D3. Без гидратации они работали, а с ней могут давать ошибку несовпадения DOM. Пока что в таком случае ставьте атрибут ngSkipHydration на компонент, который рисуется этой библиотекой.

Сторонние скрипты с работой с DOM

Многие сторонние скрипты, например трекеры рекламы и аналитика, меняют DOM до гидратации. Страница перестаёт совпадать со структурой, которую ждёт Angular, и гидратация падает с ошибкой. Такие скрипты лучше откладывать до момента после гидратации. Чтобы отложить скрипт до процессов после гидратации, смотрите AfterNextRender.

Инкрементальная гидратация

Инкрементальная гидратация — расширенный вид гидратации: момент гидратации задаётся точнее. Подробнее — в руководстве по инкрементальной гидратации.


Источник: https://angular.dev/guide/hydration

Комментарии