Какие ключевые техники оптимизации производительности Frontend вы знаете?
Вопрос
Какие основные техники оптимизации производительности frontend-приложения вы используете и как измеряете результат?
Короткий ответ
Основные рычаги — уменьшить объём и стоимость загружаемого/выполняемого кода (code splitting, lazy loading, tree shaking), уменьшить вес медиа (оптимизация изображений) и переиспользовать уже загруженное (кэширование). Результат измеряется не "на глаз", а метриками Core Web Vitals через Lighthouse/RUM.
Подробный ответ
Code splitting и lazy loading. Вместо одного огромного бандла приложение разбивается на чанки по роутам/фичам, которые подгружаются по требованию (import() под капотом сборщика). Пользователь скачивает код только для того экрана, который реально открыл, а не всё приложение целиком. То же самое применимо к тяжёлым библиотекам, нужным не сразу (редакторы, чарты, модалки) — их подгружают динамически по событию.
Tree shaking. Сборщик (Rollup/webpack/esbuild) на основе статического анализа ESM-импортов выкидывает из бандла код, который реально нигде не используется. Работает надёжно только с ESM (import/export) и без побочных эффектов на верхнем уровне модуля — CommonJS (require) статически не анализируется так же хорошо, поэтому "утяжеляет" бандл сильнее.
Оптимизация изображений. Самая частая причина тяжёлых страниц — картинки. Меры: современные форматы (WebP/AVIF вместо PNG/JPEG), srcset/sizes для отдачи нужного разрешения под устройство, loading="lazy" для картинок вне первого экрана, явные width/height (или aspect-ratio) чтобы избежать layout shift при догрузке, и CDN с автоматической ресайз/сжатием "на лету" (Nuxt Image, Cloudinary и т.п.).
Стратегии кэширования. HTTP-кэш на статике с иммутабельными именами файлов (хэш в имени → Cache-Control: max-age=31536000, immutable), Service Worker для offline/повторных визитов, HTTP/2-3 и предзагрузка критичных ресурсов (<link rel="preload">, dns-prefetch, preconnect). На уровне данных — кэширование ответов API (SWR/React Query, Nuxt useAsyncData с ключами).
Измерение. Субъективное ощущение "стало быстрее" не годится — нужны метрики:
- Lighthouse — синтетический аудит (в devtools или CI), даёт числовую оценку и конкретные рекомендации по каждому пункту.
- Core Web Vitals — реальные пользовательские метрики Google:
- LCP (Largest Contentful Paint) — как быстро отрисовался самый крупный видимый элемент.
- INP (Interaction to Next Paint, сменил FID) — отзывчивость на взаимодействия пользователя.
- CLS (Cumulative Layout Shift) — насколько сильно "прыгает" вёрстка при загрузке.
- Важно различать синтетические измерения (Lighthouse, лабораторные условия) и RUM (Real User Monitoring — метрики, собранные у реальных пользователей через
web-vitalsбиблиотеку и отправленные в аналитику), потому что реальная сеть/устройства пользователей часто сильно хуже лабораторных.
Пример
// Lazy loading тяжёлого компонента (Vue)
const ChartWidget = defineAsyncComponent(() =>
import('./ChartWidget.vue')
)
<!-- Оптимизация изображения: формат, размеры, ленивая загрузка -->
<img
src="/hero-800.webp"
srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
sizes="(max-width: 600px) 400px, 800px"
width="800"
height="450"
loading="lazy"
alt="Hero"
/>
// Сбор реальных метрик Core Web Vitals (RUM) и отправка в аналитику
import { onLCP, onINP, onCLS } from 'web-vitals'
function sendToAnalytics(metric) {
navigator.sendBeacon('/analytics', JSON.stringify(metric))
}
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
# Cache-Control для иммутабельных статических ассетов с хэшем в имени
Cache-Control: public, max-age=31536000, immutable
Дополнительные вопросы
- Чем INP отличается от устаревшей метрики FID и почему её ввели?
- Как разница между лабораторными (Lighthouse) и полевыми (RUM) метриками влияет на приоритизацию оптимизаций?
- Какие типичные причины высокого CLS и как их устранить?