Code splitting по маршрутам: где ломается скорость загрузки
Единый бандл перестаёт быть безобидным ровно в тот момент, когда приложение перерастает десяток маршрутов. Сборщик складывает в один файл и стартовую страницу, и админку, и раздел отчётов — браузер обязан скачать и разобрать всё это до первой отрисовки, даже если пользователь открыл только форму входа. Размер бандла растёт нелинейно: каждая новая тяжёлая зависимость тянет за собой транзитивные модули, а дерево зависимостей между маршрутами почти всегда пересекается.
Дальше включается эффект дублирования. Если разделы собираются независимо и без общей конфигурации, один и тот же модуль попадает в несколько чанков — браузер загружает его повторно, кэш не срабатывает, и вместо экономии трафика получается регресс. Время до интерактивности на тяжёлых маршрутах уезжает вверх: пока разбирается ненужный код, кнопки не реагируют, а пользователь считает, что интерфейс завис.
Ленивая загрузка модулей по маршрутам решает это на уровне архитектуры, а не косметики. Точки разделения ставятся на границах маршрутов, общие зависимости выносятся в отдельный чанк, а предзагрузка подключается для тех переходов, которые пользователь совершает с высокой вероятностью. Тогда стартовая страница отдаёт только свой код, а остальное подтягивается по факту навигации.
Разбираем, как это закрывается: где именно ставить границы разделения, как не породить дубли и чем измерять результат до и после.
Что даёт разделение сборки по маршрутам
Разделение сборки по маршрутам — это когда каждая страница приложения грузит только свой код, а не весь проект целиком. Вместо одного стартового бандла, который тащит на первый экран всё сразу, сборщик нарезает код на чанки и подгружает их по мере перехода пользователя. В результате стартовый бандл сокращается в разы: на первый экран приходит только то, что нужно для его отрисовки, а тяжёлые разделы — аналитика, редакторы, карты — подтягиваются позже, когда пользователь до них дошёл.
Время загрузки страницы падает не линейно, а скачком: браузер быстрее скачивает меньший объём, раньше начинает парсить и отрисовывать разметку, и первая отрисовка наступает заметно раньше. Особенно это видно на мобильных сетях и на медленных каналах, где каждый лишний килобайт стартового бандла напрямую отодвигает момент, когда пользователь видит контент.
Кэширование чанков работает в плюс к скорости: код маршрута меняется реже, чем общий каркас приложения, поэтому при обновлении одного раздела браузер переиспользует уже загруженные чанки остальных. Пользователь получает свежую версию страницы, не перекачивая всё заново, а повторные визиты проходят почти мгновенно.
Изоляция отказов — отдельная выгода, которую замечают уже в эксплуатации. Если один маршрут падает из-за ошибки в своём коде, остальные продолжают работать: сбой локализован внутри чанка, а не роняет всё приложение. Это упрощает и поиск причины, и откат — достаточно вернуть предыдущую версию конкретного раздела.
Проверяем эффект по метрикам: размер стартового бандла до и после, время до первой отрисовки, доля маршрутов, вынесенных в отдельные чанки, и число повторных загрузок при обновлении релиза. Если стартовый бандл не уменьшился или чанки дублируют общий код — разделение сделано формально, и мы это видим на цифрах, а не на словах.
Способы организации разделения: сравнение подходов
Разделение кода — это компромисс между скоростью первой отрисовки и числом сетевых запросов. Чем мельче нарезаны части, тем быстрее стартует страница, но тем больше накладных расходов на их догрузку и риск «водопада» при переходах. Ниже — три рабочие схемы, между которыми реально выбирают на практике.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Разделение по маршрутам | Крупное приложение с чёткими разделами: каталог, кабинет, админка. Каждый маршрут — отдельная точка входа через динамический импорт | Общий код дублируется между чанками, если не вынести его в общий модуль; переход между разделами даёт задержку без предзагрузки |
| Разделение по компонентам | Тяжёлые виджеты внутри одной страницы: редактор, графики, карты, модальные окна | Много мелких запросов; без грамотной предзагрузки чанков пользователь видит «мигание» при первом взаимодействии |
| Гибридная схема | Продакшн-приложения, где важны и старт, и переходы: маршруты как основа, тяжёлые компоненты — отдельными чанками | Сложнее конфигурация сборки и правила предзагрузки; нужен контроль, чтобы чанки не разрастались |
Как выбирают на практике
Для большинства проектов базой становится разделение по маршрутам: оно даёт предсказуемую структуру и понятную точку входа на каждый раздел. Поверх неё точечно выделяют тяжёлые компоненты — так первая отрисовка остаётся быстрой, а редкие сценарии не тянут за собой весь код.
Предзагрузка чанков закрывает главный минус схемы — задержку при переходе. Ссылки на вероятные следующие маршруты прогреваются заранее, и пользователь не ждёт загрузку в момент клика. Гибридная схема требует дисциплины: правила разделения фиксируются в конфигурации сборки, а размер чанков проверяется на каждом релизе, иначе разделение превращается в источник регрессий.
Этапы внедрения разделения сборки
Внедрение разделения сборки — это не «включить галочку в конфиге», а последовательность инженерных шагов с проверяемым результатом на каждом. Ниже — процесс, по которому мы ведём работы: от аудита текущего бандла до передачи с метриками до/после.
- Аудит сборки. Разбираем текущий бандл по чанкам: смотрим, что попадает в стартовый загрузчик, какие зависимости тянут за собой тяжёлые библиотеки, где дублируются модули. На выходе — карта «что грузится всегда, а что нужно только на конкретном экране» и список очевидных кандидатов на вынос.
- Схема границ маршрутов. Проектируем, где именно разрезать приложение: по маршрутам верхнего уровня, по вложенным разделам или по сценариям внутри одного экрана. Фиксируем общие модули (авторизация, шапка, дизайн-система) в отдельный общий чанк, чтобы они не дублировались в каждом куске.
- Настройка сборщика. Переводим схему в конфигурацию: динамические импорты на границах маршрутов, именование чанков, вынос вендорных зависимостей, предзагрузка вероятных следующих экранов. Отдельно настраиваем поведение при обрыве загрузки чанка — пользователь не должен видеть белый экран.
- Проверка метрик. Сравниваем до/после: объём стартового бандла, время до первого рендера, число запросов на первый экран, доля кода, загружаемого по требованию. Смотрим не средние, а худший сценарий — медленное соединение и холодный кеш.
- Регресс и краевые случаи. Прогоняем переходы между маршрутами, прямые заходы по глубоким ссылкам, возврат по истории, откат на предыдущую версию приложения при незакрытых чанках.
- Документация и передача. Отдаём схему границ, конфигурацию сборщика и правила добавления новых маршрутов, чтобы следующий разработчик не сломал разбиение одной строкой импорта.
Каждый этап заканчивается артефактом, который можно проверить, а не устным «готово».
Конфигурации, схемы маршрутов и регламенты на выходе
Разбиение по маршрутам живёт не в коде, а в конфигурации и договорённостях. Если границы чанков заданы только «на глаз» в одном файле сборщика, через пару релизов никто не вспомнит, почему маршрут оплаты грузится вместе с каталогом, а новый разработчик добавит страницу в общий бандл. Поэтому мы фиксируем результат в артефактах, которые читаются без нас.
- Конфигурация сборки — правила разбиения: какие маршруты получают отдельный чанк, что выносится в общий (общие компоненты, клиент API, стили), как называются файлы. С комментариями, почему граница проведена именно здесь.
- Карта маршрутов — таблица «маршрут → чанк → что в нём лежит → когда грузится». По ней видно, где пересекаются зависимости и какой маршрут тянет за собой лишнее.
- Правила кэширования — заголовки для хешированных чанков и для точек входа: неизменяемые файлы кэшируются надолго, точка входа перепроверяется. Отдельно — поведение при выкатке, чтобы пользователь не получил старую точку входа с новыми чанками.
- Предзагрузка и приоритеты — какие маршруты подгружаются заранее по навигации, какие по наведению, какие только по клику. Это осознанное решение, а не побочный эффект.
- Документация — как читать карту маршрутов, где смотреть размеры чанков, что делать при росте бандла.
- Регламент добавления маршрутов — пошаговый чек-лист: завести маршрут, назначить чанк, проверить пересечения, обновить карту, прогнать проверку размеров в сборке.
- Проверки в сборке — автоматический контроль: если чанк превысил порог или маршрут попал в общий бандл, сборка предупреждает. Правило работает без напоминаний.
Такой набор переживает смену подрядчика: границы чанков, кэш и порядок добавления страниц описаны явно, а не восстанавливаются по истории коммитов.
Кейс: сокращение стартовой загрузки личного кабинета
Личный кабинет банковского клиента: единый стартовый бандл на 2.8 МБ, время до интерактивности на среднем мобильном канале — 6.4 с. На медленных каналах (3G, перегруженный Wi-Fi в торговых центрах) доля отказов до первого взаимодействия доходила до 23% — пользователь видел белый экран и уходил раньше, чем приложение успевало отрисоваться.
Разобрали маршруты по частоте использования: вход, баланс, история операций, переводы, настройки. Каждый маршрут получил собственную точку входа, общий код вынесен в отдельный чанк, тяжёлые виджеты (графики, редактор реквизитов) подгружаются по требованию через динамический импорт. Предзагрузка соседних маршрутов включается только после первого взаимодействия — на медленном канале это не отбирает полосу у критичного кода.
Метрики до и после
| Метрика | До | После |
|---|---|---|
| Стартовый бандл | 2.8 МБ | 640 КБ |
| Время до интерактивности (средний мобильный) | 6.4 с | 2.1 с |
| Отказы до первого взаимодействия | 23% | 7% |
Стартовый бандл сократился в 4.4 раза — на экран входа теперь грузится только форма авторизации и общий рантайм. Время до интерактивности упало втрое, а доля отказов на медленных каналах — с 23% до 7%. Конверсия в первый вход выросла на 11 п.п. без изменений в самом сценарии авторизации.
Отдельно настроили метрики производительности в реальном времени: разбивка по маршрутам, время загрузки каждого чанка, доля пользователей, у которых интерактивность не наступает за 4 с. Это позволяет ловить регрессии при каждом релизе, а не через месяц по жалобам. Инструмент — webpack-bundle-analyzer для контроля состава бандлов на этапе сборки.
Чем разделение по маршрутам отличается от разделения по компонентам?
Разделение по маршрутам — это стратегия, при которой точка входа в приложение и каждый экран верхнего уровня собираются в отдельный чанк, а переход между разделами подгружает нужный кусок по требованию. Разделение по компонентам работает на уровень ниже: дробятся отдельные блоки внутри одного экрана — модальные окна, тяжёлые виджеты, редакторы. Разница не в инструменте, а в гранулярности чанков и в том, кто управляет моментом загрузки: при маршрутном делении это делает навигация, при компонентном — логика самого интерфейса.
Гранулярность напрямую определяет накладные расходы. Каждый чанк — это отдельный сетевой запрос со своим заголовком, рукопожатием и временем до первого байта. Пять-семь маршрутных чанков дают предсказуемую картину: пользователь скачивает ровно тот раздел, куда зашёл. Дробление по компонентам легко уходит в десятки мелких файлов, и на медленном соединении суммарные накладные расходы начинают съедать выигрыш от меньшего объёма — особенно когда компоненты грузятся каскадом, один за другим.
Кэш браузера — второй водораздел. Маршрутные чанки меняются редко и точечно: правка в одном разделе не инвалидирует остальные, поэтому при следующем деплое пользователь перекачивает только затронутый кусок, а всё остальное берётся из кэша. При компонентном делении хеши файлов пересчитываются чаще, и граница между «обновилось» и «осталось прежним» размывается — растёт доля повторно скачанного.
На практике стратегии не конкурируют, а вкладываются друг в друга. Маршрут задаёт крупные стабильные чанки и предсказуемый кэш, а внутри тяжёлого экрана точечно выделяются отдельные компоненты — те, что нужны не сразу или редко. Такой порядок держит число запросов под контролем и сохраняет выигрыш от отложенной загрузки там, где он реально заметен.
Технические сомнения перед внедрением
Разделение сборки по маршрутам меняет то, как браузер получает код: вместо одного крупного файла он подгружает чанки по мере перехода пользователя. Это затрагивает индексацию, кэширование и откат релизов — разберём каждый пункт отдельно.
Не пострадает ли SEO, если часть кода грузится по требованию?
Поисковые роботы исполняют JavaScript и видят отрисованное содержимое, поэтому отложенная загрузка чанков сама по себе не скрывает контент. Риск возникает в другом: если точка входа маршрута подгружается только по клику, робот может не дойти до неё. Поэтому критичные маршруты мы отдаём с серверным рендерингом или пререндером, а ссылки в разметке оставляем настоящими — тогда индексация не зависит от того, сработал ли переход. После внедрения сверяем карту сайта с фактически проиндексированными адресами и покрытие в панели вебмастера.
Как работает предзагрузка и не съедает ли она выгоду от разделения?
Предзагрузка запускается на простаивающем канале и по намерению: наведение или касание ссылки, появление её в зоне видимости. Приоритет у чанка текущего маршрута, остальное догружается фоном. Если предзагружать всё подряд, эффект разделения теряется — поэтому список прогрева ограничиваем 2-3 вероятными переходами и пересматриваем по данным аналитики.
Что с инвалидацией кэша при обновлении?
Имена чанков привязаны к содержимому, поэтому новый релиз получает новые адреса, а старые остаются в кэше браузера и не мешают. Точку входа и разметку отдаём с запретом долгого кэширования — иначе пользователь застрянет на прежней версии. При выкладке следим, чтобы ссылки на чанки и сами файлы попадали в хранилище одной операцией.
Как откатывать изменения, если релиз дал регрессию?
Откат — это возврат прежней точки входа и прежнего набора чанков, они не перезаписываются. Держим несколько последних сборок, поэтому возврат занимает минуты и не требует пересборки. Перед выкладкой прогоняем дымовые проверки по маршрутам, чтобы ловить обрывы загрузки чанков до пользователей.
Если какой-то из этих сценариев у вас уже болел — опишите задачу, вернёмся с оценкой и планом под ваш стек.
Обсудим вашу задачу по разделению сборки
Чтобы вернуться с оценкой и планом внедрения, а не с общими словами, нам нужны исходные данные по вашему приложению. Чем точнее вводные, тем меньше допущений в смете и тем реалистичнее сроки. Ниже — что уточняем и что ждём от вас.
Что уточним на нашей стороне:
- текущую структуру сборки: один общий бандл или уже есть ручное разделение на несколько файлов;
- карту маршрутов и точек входа — какие экраны грузятся первыми, какие редко;
- объём и состав зависимостей: что тянется в стартовый чанк и можно ли это отложить;
- наличие серверного рендеринга и то, как он сочетается с отложенной загрузкой;
- требования к поведению при переходах: индикаторы загрузки, предзагрузка вероятных маршрутов;
- текущий процесс релизов и то, как выкатываются изменения без простоя.
Что нужно от вас для оценки работ:
- доступ к репозиторию или выгрузка конфигурации сборки и карты маршрутов;
- описание целевых метрик: время до интерактивности, вес стартового чанка, доля отложенного кода;
- перечень браузеров и устройств, которые обязаны поддерживаться;
- ограничения по инфраструктуре: где хостится статика, есть ли CDN, как настроено кеширование.
Как вернёмся с планом внедрения. Разложим работу на этапы: аудит текущей сборки, проектирование границ разделения по маршрутам, настройка отложенной загрузки и предзагрузки, проверка поведения при навигации и откат при регрессии. К каждому этапу приложим ожидаемый эффект в измеримых величинах — вес стартового чанка, время до интерактивности, доля кода, загружаемого по требованию.
Опишите задачу в свободной форме — что за приложение, какие маршруты и что беспокоит в текущей загрузке. Мы вернёмся с оценкой работ и планом внедрения по этапам.







