Внести вклад
Подробные руководства
Серверный и гибридный рендеринг

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

Инкрементальная гидратация — продвинутый тип гидратации, при котором части приложения могут оставаться дегидратированными и постепенно гидратироваться по мере необходимости.

Зачем использовать инкрементальную гидратацию?

Инкрементальная гидратация — улучшение производительности поверх полной гидратации приложения. Она может давать меньшие начальные бандлы при UX, сопоставимом с полной гидратацией. Меньшие бандлы ускоряют начальную загрузку, снижая First Input Delay (FID) и Cumulative Layout Shift (CLS).

Инкрементальная гидратация также позволяет использовать откладываемые views (@defer) для контента, который раньше нельзя было откладывать. В частности, теперь можно использовать @defer для контента above the fold. До инкрементальной гидратации размещение @defer above the fold приводило к рендеру placeholder и последующей замене основным шаблоном блока @defer — это вызывало сдвиг макета. С инкрементальной гидратацией основной шаблон блока @defer рендерится без сдвига макета при гидратации.

Как включить инкрементальную гидратацию в Angular?

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

Инкрементальная гидратация включена по умолчанию при использовании provideClientHydration().

import {bootstrapApplication, provideClientHydration} from '@angular/platform-browser';

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

ПРИМЕЧАНИЕ: Инкрементальная гидратация зависит от повтора событий и включает его автоматически. Если у вас уже есть withEventReplay() в списке, его можно безопасно удалить.

Чтобы отказаться от инкрементальной гидратации, используйте withNoIncrementalHydration():

Как работает инкрементальная гидратация?

Инкрементальная гидратация строится поверх полной гидратации приложения, откладываемых views и повтора событий. С инкрементальной гидратацией к блокам @defer можно добавлять дополнительные триггеры, задающие границы инкрементальной гидратации. Добавление триггера hydrate к defer-блоку говорит Angular загрузить зависимости этого блока при SSR и отрендерить основной шаблон вместо @placeholder. При клиентском рендеринге зависимости по-прежнему откладываются, а контент defer-блока остаётся дегидратированным, пока не сработает триггер hydrate. Этот триггер заставляет defer-блок загрузить зависимости и гидратировать контент. Любые события браузера — в частности те, что соответствуют слушателям, зарегистрированным в компоненте, — вызванные пользователем до гидратации, ставятся в очередь и воспроизводятся после завершения гидратации.

Управление гидратацией контента с помощью триггеров

Можно задать hydrate-триггеры, которые контролируют, когда Angular загружает и гидратирует отложенный контент. Это дополнительные триггеры наряду с обычными триггерами @defer.

У каждого блока @defer может быть несколько hydrate-триггеров, разделённых точкой с запятой (;). Angular запускает гидратацию, когда срабатывает любой из триггеров.

Есть три типа hydrate-триггеров: hydrate on, hydrate when и hydrate never.

hydrate on

hydrate on задаёт условие, при котором запускается гидратация блока @defer.

Доступные триггеры:

Триггер Описание
hydrate on idle Срабатывает, когда браузер простаивает. Поддерживает опциональный timeout.
hydrate on viewport Срабатывает, когда указанный контент попадает во viewport
hydrate on interaction Срабатывает при взаимодействии пользователя с указанным элементом
hydrate on hover Срабатывает при наведении мыши на указанную область
hydrate on immediate Срабатывает сразу после рендера не-отложенного контента
hydrate on timer Срабатывает через заданный интервал

hydrate on idle

Триггер hydrate on idle загружает зависимости откладываемого view и гидратирует контент, когда браузер переходит в idle-состояние на основе requestIdleCallback.

Опционально можно указать timeout в миллисекундах, который передаётся в requestIdleCallback. Если браузер не запланирует callback достаточно быстро, работа выполнится не позже указанного timeout.

@defer (hydrate on idle) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

<!-- With a 500ms timeout -->
@defer (hydrate on idle(500)) {
  <large-cmp />
}

hydrate on viewport

Триггер hydrate on viewport загружает зависимости откладываемого view и гидратирует соответствующую часть приложения, когда указанный контент попадает во viewport, используя Intersection Observer API.

@defer (hydrate on viewport) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on interaction

Триггер hydrate on interaction загружает зависимости откладываемого view и гидратирует контент, когда пользователь взаимодействует с указанным элементом через события click или keydown.

@defer (hydrate on interaction) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on hover

Триггер hydrate on hover загружает зависимости откладываемого view и гидратирует контент, когда мышь наведена на область триггера через события mouseover и focusin.

@defer (hydrate on hover) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on immediate

Триггер hydrate on immediate загружает зависимости откладываемого view и гидратирует контент немедленно. То есть отложенный блок загружается сразу после того, как весь остальной не-отложенный контент закончил рендериться.

@defer (hydrate on immediate) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on timer

Триггер hydrate on timer загружает зависимости откладываемого view и гидратирует контент через заданный интервал.

@defer (hydrate on timer(500ms)) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

Параметр длительности должен быть указан в миллисекундах (ms) или секундах (s).

hydrate when

Триггер hydrate when принимает пользовательское условное выражение и загружает зависимости откладываемого view и гидратирует контент, когда условие становится truthy.

@defer (hydrate when condition) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

ПРИМЕЧАНИЕ: Условия hydrate when срабатывают только для самого верхнего дегидратированного блока @defer. Условие триггера задаётся в родительском компоненте, который должен существовать до срабатывания. Если родительский блок дегидратирован, Angular ещё не сможет разрешить это выражение.

hydrate never

hydrate never позволяет указать, что контент в defer-блоке должен оставаться дегидратированным бесконечно, фактически становясь статическим контентом. Это относится только к начальному рендеру. При последующем клиентском рендере блок @defer с hydrate never всё равно загрузит зависимости, так как гидратация применяется только к начальной загрузке SSR-контента. В примере ниже при последующих клиентских рендерах зависимости блока @defer загрузятся по viewport.

@defer (on viewport; hydrate never) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

ПРИМЕЧАНИЕ: Использование hydrate never предотвращает гидратацию всего вложенного поддерева данного блока @defer. Другие hydrate-триггеры для контента внутри этого блока не сработают.

Hydrate-триггеры вместе с обычными триггерами

Hydrate-триггеры — дополнительные триггеры наряду с обычными триггерами блока @defer. Гидратация — оптимизация начальной загрузки, поэтому hydrate-триггеры применяются только к ней. Любой последующий клиентский рендер по-прежнему использует обычный триггер.

@defer (on idle; hydrate on interaction) {
  <example-cmp />
} @placeholder {
  <div>Example Placeholder</div>
}

В этом примере при начальной загрузке применяется hydrate on interaction. Гидратация запустится при взаимодействии с компонентом <example-cmp />. При любой последующей загрузке страницы с клиентским рендерингом — например, когда пользователь кликает по routerLink, загружающему страницу с этим компонентом, — применится on idle.

Как инкрементальная гидратация работает со вложенными блоками @defer?

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

@defer (hydrate on interaction) {
  <parent-block-cmp />
  @defer (hydrate on hover) {
    <child-block-cmp />
  } @placeholder {
    <div>Child placeholder</div>
  }
} @placeholder {
  <div>Parent Placeholder</div>
}

В примере выше наведение на вложенный блок @defer запускает гидратацию. Сначала гидратируется родительский блок @defer с <parent-block-cmp />, затем — дочерний блок @defer с <child-block-cmp />.

Ограничения

У инкрементальной гидратации те же ограничения, что и у полной гидратации приложения, включая ограничения на прямую манипуляцию DOM и требование валидной HTML-структуры. Подробнее — в разделе Ограничения руководства по гидратации.

Нужно ли по-прежнему указывать блоки @placeholder?

Да. Контент блока @placeholder не используется для инкрементальной гидратации, но @placeholder всё равно нужен для последующих случаев клиентского рендеринга. Если контент не был на маршруте начальной загрузки, любая навигация к маршруту с вашим блоком @defer отрендерит его как обычный блок @defer. Поэтому в таких случаях клиентского рендеринга рендерится @placeholder.