Angular

Ленивая загрузка маршрутов

Приложение растёт, и однажды начальный бандл раздувается до мегабайтов: пользователь ждёт загрузки кода админки и настроек, хотя открыл всего лишь главную. Ленивая загрузка решает это на уровне маршрута. Вместо прямой ссылки на компонент маршрут получает функцию динамического импорта, и сборщик автоматически выносит этот код в отдельный чанк. Чанк скачивается только тогда, когда пользователь реально переходит на маршрут. Начальная загрузка становится тонкой, а тяжёлые разделы платят за себя сами в момент захода.

  • Раздел админки: код панели управления грузится только у админов при заходе на /admin, обычные пользователи его не качают
  • Angular CLI: динамический import() в loadComponent автоматически порождает отдельный чанк при сборке
  • Крупные дашборды: каждый функциональный модуль выносится в свой чанк, начальный бандл остаётся компактным
  • Стратегии предзагрузки: PreloadAllModules дотягивает чанки в фоне после отрисовки главной, делая последующие переходы мгновенными
  • Мобильный трафик: тонкий первый бандл ускоряет первую отрисовку, что напрямую влияет на метрики Core Web Vitals

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

  • Базовая маршрутизация: provideRouter, Routes, router-outlet
  • Динамический import() в JavaScript и понятие чанка сборки
  • Standalone-компоненты и их экспорт

loadComponent: ленивый компонент

В нестрогой конфигурации маршрут ссылается на компонент напрямую: component: AdminComponent. Такой импорт статичен, поэтому компонент попадает в начальный бандл. Ленивая версия заменяет прямую ссылку на loadComponent с функцией динамического импорта. Сборщик видит import() и автоматически выносит код в отдельный чанк, который скачается лишь при переходе на маршрут.

Функция в loadComponent вызывается маршрутизатором в момент активации маршрута. Она возвращает промис с компонентом. Браузер скачивает чанк, маршрутизатор подставляет компонент в outlet. Если на маршрут так и не зайдут, чанк вообще не загрузится, экономя трафик и время старта.

Ленивая загрузка работает именно благодаря статически анализируемому import(). Сборщик распознаёт литерал импорта на этапе компиляции и режет код на чанки. Если путь импорта собрать из переменной, статический анализ ломается и деление не происходит.

Почему компонент, подключённый через loadComponent: () => import('./admin.component'), не попадает в начальный бандл?

loadChildren: ленивый раздел

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

  • loadComponent — Лениво грузит один standalone-компонент. Подходит для отдельного экрана без вложенных маршрутов.
  • loadChildren — Лениво грузит массив дочерних маршрутов из отдельного файла. Подходит для целого раздела со своей внутренней навигацией.

В standalone-эпоху loadChildren импортирует не NgModule, а массив маршрутов (Routes). Это упрощает структуру: раздел - это файл с конфигурацией маршрутов и набор standalone-компонентов, без модуля-обёртки.

Нужно лениво загрузить целый раздел /admin с тремя вложенными экранами и внутренней навигацией. Что подходит лучше?

Стратегии предзагрузки и баланс

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

СтратегияПоведениеКогда уместна
NoPreloading (по умолчанию)Чанк грузится только при заходе на маршрутМного редких разделов, важен минимальный трафик
PreloadAllModulesВсе ленивые чанки дотягиваются в фоне после стартаНесколько разделов, важна мгновенность переходов
Своя стратегияПредзагружаются только помеченные маршруты (например, по data)Точечный контроль: грузить вероятные разделы, пропускать редкие

Баланс простой. Чем мельче деление, тем тоньше начальный бандл и быстрее первая отрисовка, но тем чаще пользователь ждёт чанк при переходе. Предзагрузка переносит это ожидание в фон. Разумная отправная точка: лениво грузить крупные и редкие разделы, а часто посещаемые - предзагружать после старта.

PreloadAllModules дотягивает все чанки и на медленной мобильной сети может съесть трафик ради разделов, куда пользователь не зайдёт. Если разделов много и они разные по востребованности, своя стратегия предзагрузки по флагу в route data точнее, чем загрузка всего подряд.

В чём основной компромисс между ленивой загрузкой без предзагрузки и стратегией PreloadAllModules?

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

Урок про разбиение кода по маршрутам. Дальше - выбор способа рендеринга каждого:

  • Angular Router: маршруты — Та же конфигурация Routes, но компонент подгружается динамическим импортом
  • Режимы рендеринга маршрутов — Каждому маршруту можно назначить prerender, SSR или client-side рендеринг

Итог

  • loadComponent: () => import('...') лениво грузит один standalone-компонент, сборщик выносит его в отдельный чанк
  • loadChildren лениво грузит группу маршрутов из отдельного файла, удобно для целого раздела приложения
  • Деление кода по маршрутам уменьшает начальный бандл: пользователь качает только код открытого экрана
  • Стратегии предзагрузки (withPreloading) дотягивают чанки в фоне после первой отрисовки, ускоряя переходы
  • Баланс: агрессивное деление уменьшает первый бандл, но добавляет задержку при первом заходе на маршрут; предзагрузка сглаживает это

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

  • ng-22-router-intro — Ленивая загрузка - это та же конфигурация Routes, но с динамическим импортом вместо прямой ссылки на компонент
  • ng-25-render-modes — Деление по маршрутам - предпосылка для назначения каждому маршруту своего режима рендеринга
Ленивая загрузка маршрутов

0

1

Войти