Современные SPA-приложения становятся всё сложнее: растёт количество страниц, компонентов, библиотек и бизнес-логики. Если загрузить весь JavaScript-код сразу при открытии приложения, пользователь может столкнуться с долгим стартом, особенно на мобильных устройствах и при медленном соединении.
Один из наиболее эффективных способов решить эту проблему — Lazy Loading, или динамическая подгрузка компонентов. Вместо того чтобы отправлять браузеру весь код приложения сразу, мы загружаем необходимые части только тогда, когда они действительно понадобились.
Что такое Lazy Loading
При обычной сборке SPA браузер может получить один или несколько крупных JavaScript-бандлов:
index.html
↓
main.js — всё приложение
↓
Главная
Профиль
Настройки
Каталог
Админка
Редактор
Графики
...
Даже если пользователь откроет только главную страницу, браузеру приходится загрузить и обработать код остальных разделов.
При Lazy Loading приложение разбивается на отдельные чанки:
index.html
↓
main.js
↓
Главная
Пользователь открыл /profile
↓
profile.chunk.js
Пользователь открыл /admin
↓
admin.chunk.js
В результате первоначальная загрузка становится меньше, а дополнительные части приложения загружаются по мере необходимости.
Почему это ускоряет SPA
Главное преимущество — уменьшение объёма JavaScript, который браузеру необходимо загрузить, распарсить и выполнить при первом открытии страницы.
Допустим, приложение после сборки содержит 2 МБ JavaScript-кода. При классическом подходе пользователь может получить все 2 МБ сразу.
После разделения на чанки ситуация может выглядеть так:
| Часть приложения | Размер |
|---|---|
| Основной bundle | 400 КБ |
| Каталог | 350 КБ |
| Профиль | 180 КБ |
| Админка | 500 КБ |
| Редактор | 570 КБ |
Пользователь, который заходит только в каталог, загружает основной код и чанк каталога, а не весь 2-мегабайтный bundle.
Особенно заметен эффект на:
- мобильных устройствах;
- медленных сетях;
- больших корпоративных SPA;
- приложениях с тяжёлыми библиотеками;
- страницах, которыми пользуется только небольшая часть аудитории.
Lazy Loading компонентов в React
В React для динамической загрузки компонентов используется React.lazy() вместе с Suspense.
Например:
import { lazy, Suspense } from 'react';
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<Settings />
</Suspense>
);
}
Важная часть здесь:
import('./Settings')
Это динамический import(). Современные сборщики воспринимают его как границу разделения кода и создают отдельный chunk.
Пока компонент не понадобился, его код не загружается.
Lazy Loading маршрутов
На практике наиболее полезно применять ленивую загрузку не отдельных маленьких компонентов, а целых страниц и маршрутов.
Например:
const Home = lazy(() => import('./pages/Home'));
const Products = lazy(() => import('./pages/Products'));
const Profile = lazy(() => import('./pages/Profile'));
const Admin = lazy(() => import('./pages/Admin'));
Маршруты можно организовать так, чтобы каждый крупный раздел приложения загружался отдельно.
Это особенно эффективно для административных панелей, личных кабинетов, редакторов и других функциональных областей, которые не нужны каждому пользователю.
Lazy Loading во Vue
Во Vue асинхронный компонент можно создать с помощью defineAsyncComponent:
import { defineAsyncComponent } from 'vue';
const Settings = defineAsyncComponent(() =>
import('./components/Settings.vue')
);
Vue и современные инструменты сборки также позволяют разделять приложение на отдельные чанки.
Для маршрутов часто используется динамический импорт непосредственно в конфигурации роутера:
{
path: '/settings',
component: () => import('./views/Settings.vue')
}
Теперь код Settings.vue может быть загружен только при переходе пользователя на соответствующий маршрут.
Lazy Loading в Angular
Angular активно использует lazy loading для маршрутизации.
Например:
{
path: 'admin',
loadComponent: () =>
import('./admin/admin.component')
.then(m => m.AdminComponent)
}
В более крупных приложениях лениво загружаются целые feature-модули или наборы маршрутов.
Такой подход позволяет не включать код административной части приложения в первоначальную загрузку для обычного пользователя.
Что именно стоит загружать лениво
Не каждый компонент необходимо делать lazy.
Хорошие кандидаты:
1. Редкие страницы
Например, настройки аккаунта или страница администрирования.
2. Тяжёлые компоненты
Графики, редакторы, карты, сложные таблицы и визуализации.
3. Крупные библиотеки
Если библиотека нужна только в одном разделе, нет смысла включать её в первоначальный bundle.
Например:
const loadEditor = () => import('./editor');
4. Функциональность, доступная только части пользователей
Админ-панель — классический пример. Обычному пользователю не нужно загружать её JavaScript-код.
Не стоит делать Lazy Loading абсолютно всего
Слишком агрессивное разделение кода тоже может ухудшить производительность.
Представим приложение, которое разбито на десятки маленьких компонентов. Вместо одного оптимального запроса браузер начинает выполнять множество запросов за небольшими файлами.
Кроме того, пользователь может заметить задержку непосредственно в момент перехода:
Нажатие на кнопку
↓
Запрос чанка
↓
Загрузка JavaScript
↓
Инициализация компонента
↓
Отображение страницы
Поэтому хороший принцип — разделять приложение по функциональным границам, а не превращать каждый компонент в отдельный chunk.
Предзагрузка компонентов
Lazy Loading можно дополнить предварительной загрузкой.
Например, пользователь находится на главной странице и наводит курсор на ссылку «Профиль». В этот момент можно заранее загрузить код профиля.
Идея:
Пользователь открыл главную
↓
Основной код загружен
↓
Пользователь проявил интерес к профилю
↓
Начинаем загрузку profile.chunk.js
↓
Пользователь нажал «Профиль»
↓
Компонент уже почти готов
Это позволяет совместить экономию первоначального трафика с быстрым переходом между страницами.
Однако предзагрузку следует использовать осознанно: если загружать всё заранее, преимущество Lazy Loading быстро исчезает.
Loading UI и обработка ошибок
При динамической загрузке всегда возможна задержка или ошибка сети. Поэтому приложение должно корректно обрабатывать состояние загрузки.
Минимальный вариант:
<Suspense fallback={<Loader />}>
<Profile />
</Suspense>
Но для production-приложения желательно учитывать и ошибки загрузки чанка.
Например, ситуация может выглядеть так:
Пользователь открывает страницу
↓
Браузер запрашивает chunk
↓
Сеть недоступна
↓
Chunk не загружен
↓
Показываем понятное сообщение
Особенно важно это при деплое новых версий приложения, когда старый клиент может попытаться запросить JavaScript-файл, которого уже нет на сервере.
Lazy Loading и Core Web Vitals
Динамическая загрузка может положительно влиять на показатели производительности, поскольку уменьшает первоначальный объём JavaScript.
Но важно понимать: Lazy Loading сам по себе не гарантирует улучшение всех метрик.
На итоговую производительность влияют:
- размер initial bundle;
- количество JavaScript;
- время выполнения JavaScript;
- скорость сервера;
- размер CSS;
- изображения;
- кеширование;
- архитектура приложения;
- количество сетевых запросов.
Поэтому после внедрения Lazy Loading стоит измерять реальные показатели, а не ориентироваться только на размер bundle.
Как проверить результат
Большинство современных сборщиков позволяют посмотреть состав итоговых чанков.
Полезно сравнить:
До:
main.js — 2.1 MB
После:
main.js — 420 KB
products.js — 310 KB
profile.js — 180 KB
admin.js — 520 KB
editor.js — 670 KB
Но ещё важнее проверить, что происходит в браузере.
В DevTools можно открыть вкладку Network, перезагрузить страницу и посмотреть, какие JavaScript-файлы загружаются при старте.
Затем перейти в другой раздел приложения и убедиться, что соответствующий chunk загружается только в этот момент.
Практический подход
Для большинства SPA разумная стратегия выглядит так:
- Определить основные функциональные разделы приложения.
- Разделить крупные маршруты на отдельные chunks.
- Вынести тяжёлые и редко используемые компоненты в динамические импорты.
- Не делать lazy loading для маленьких компонентов, которые используются практически везде.
- Добавить понятный loading state.
- Предусмотреть обработку ошибок загрузки.
- При необходимости использовать preload/prefetch для вероятных следующих маршрутов.
- Проверить размер bundle после сборки.
- Измерить реальную производительность до и после изменений.
Итог
Lazy Loading — один из базовых инструментов оптимизации современных SPA. Его основная идея проста: не заставлять пользователя загружать JavaScript, который ему пока не нужен.
Наиболее заметный эффект обычно даёт динамическая загрузка маршрутов и крупных функциональных блоков. В результате первоначальный bundle становится меньше, приложение быстрее запускается, а тяжёлая функциональность загружается только по мере необходимости.
При этом Lazy Loading не должен превращаться в самоцель. Оптимальная архитектура — это баланс между размером initial bundle, количеством чанков, скоростью навигации и реальным сценарием использования приложения.
В сочетании с code splitting, кешированием, оптимизацией изображений и контролем JavaScript Lazy Loading позволяет сделать SPA значительно быстрее и отзывчивее для пользователя.



