Оптимизация Web Performance в Next.js: Приёмы для 90+ в Lighthouse
#NextJS #WebPerformance #Lighthouse #CoreWebVitals #Optimization #Frontend #React
Раздражены медленной загрузкой и прыгающим контентом? Разбираем battle-tested техники оптимизации Next.js: от стратегий кеширования до продвинутой работы с изображениями. Результаты проекта: LCP 0.8s (-70%), CLS 0.02 (-94%), Lighthouse 98/100.

Почему 90+ в Lighthouse — не роскошь
Данные для e-commerce (мобильные устройства):
- ├─LCP > 2.5s → -32% конверсий
- ├─CLS > 0.1 → -22% времени на сайте
- └─TBT > 200ms → -15% retention
Источник: Google CrUX, 2024
Разбор метрик Core Web Vitals
| Метрика | Что измеряет | Цель | Инструменты Next.js |
|---|---|---|---|
| LCP (Largest Contentful Paint) | Скорость загрузки главного контента | < 2.5s | next/image, priority, Preload |
| FID (First Input Delay) | Отзывчивость интерфейса | < 100ms | Код-сплиттинг, Web Workers |
| CLS (Cumulative Layout Shift) | Визуальная стабильность | < 0.1 | Размеры изображений, резервные контейнеры |
| INP (Interaction to Next Paint) | Общая отзывчивость | < 200ms | Оптимизация JavaScript, Дебаунсинг |
| TBT (Total Blocking Time) | Блокировка главного потока | < 200ms | Динамические импорты, Tree Shaking |
Базовые оптимизации (5-минутные победы)
1. Оптимизация изображений:
До
<img src="/product.jpg" alt="Product" />После
import Image from 'next/image';
<Image
src="/product.jpg"
alt="Product"
width={600}
height={400}
priority // Для LCP-элементов
placeholder="blur"
blurDataURL="data:image/svg+xml;base64,..."
/>Эффект: -45% размера изображений, +20 баллов Performance
2. Предзагрузка критических ресурсов:
_document.tsx
import Head from 'next/head';
<Head>
<link rel="preload" href="/fonts/Inter.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
<link rel="preload" href="/css/global.css" as="style" />
</Head>3. Статическое кеширование:
next.config.js
module.exports = {
headers: async () => [
{
source: '/:all*(svg|jpg|webp)',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }
]
}
]
};Advanced-оптимизации (серьёзный прирост)
4. Стратегический код-сплиттинг:
.tsx
// Динамические импорты для тяжёлых компонентов
const ProductCarousel = dynamic(
() => import('@/components/ProductCarousel'),
{
ssr: false,
loading: () => <Skeleton height={300} />
}
);
// В route-группах (App Router)
const SearchModal = dynamic(() => import('./SearchModal'), {
loading: () => <Spinner />,
});5. Оптимизация шрифтов:
layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
export default function RootLayout({ children }) {
return (
<html lang="ru" className={inter.variable}>
<body>{children}</body>
</html>
);
}6. Уменьшение JavaScript-бюджета:
bash
# Анализ бандла
npx next bundle-analyzeЗамена тяжелых библиотек:
- Moment.js → date-fns (67% меньше)
- Lodash → lodash-es + tree shaking
- Axios → нативный fetch (0kB)
7. Продвинутое кеширование ISR:
[id].tsx
// Страницы продукта
export async function getStaticProps({ params }) {
return {
props: { product: await getProduct(params.id) },
revalidate: 3600, // Ревалидация каждый час
};
}
export async function getStaticPaths() {
return { paths: [], fallback: 'blocking' };
}Инструменты мониторинга
1. Встроенные метрики Next.js:
layout.tsx
// app/layout.tsx
import { SpeedInsights } from '@vercel/speed-insights/next';
export default function Layout({ children }) {
return (
<html>
<body>
{children}
<SpeedInsights /> // Реальные данные пользователей
</body>
</html>
);
}2. Lighthouse CI в пайплайне:
lighthouse.yml
name: Lighthouse
on: [push]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run build
- run: npm run start &
- uses: treosh/lighthouse-ci-action@v9
with:
urls: ['http://localhost:3000']
budgetPath: './lighthouse-budget.json'3. Бюджет производительности:
lighthouse-budget.json
{
"performance": {
"categories": {
"largest-contentful-paint": { "budget": 1500 },
"cumulative-layout-shift": { "budget": 0.05 },
"total-blocking-time": { "budget": 200 }
}
}
}Результаты оптимизации (реальный кейс)
| Метрика | До | После | Улучшение |
|---|---|---|---|
| LCP | 2.8s | 0.8s | ⚡ 71% |
| CLS | 0.35 | 0.02 | 🎯 94% |
| TBT | 420ms | 90ms | 🚀 78% |
| Размер страницы | 1.9 MB | 540 KB | 📦 71% |
| Lighthouse (моб) | 58 | 98 | +40 баллов |
Антипаттерны, убивающие производительность
1. Безразмерные изображения
// ❌ CLS гарантирован
<Image src="/banner.jpg" alt="Sale" width={1200} height={0} />2. Рендеринг 1000+ элементов без виртуализации
// ✅ Решение
import { FixedSizeList } from 'react-window';
<FixedSizeList height={600} itemSize={46} itemCount={1000}>
{({ index, style }) => <div style={style}>Item {index}</div>}
</FixedSizeList>3. Блокирующие сторонние скрипты
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: "script-src 'self' 'unsafe-inline' https://analytics.example.com"
}
]
}
];
}
};Контрольный список для 90+
Изображения:
- <next/image> с размерами
- priority для LCP-элементов
- WebP/AVIF форматы
Шрифты:
- Локальное подключение
- display: swap
- Прелоад критических шрифтов
Код:
- Бандл < 150kB
- Динамические импорты
- Анализ через bundle-analyzer
Кеширование:
- ISR для динамических страниц
- CDN для статики
- Service Workers для PWA
Мониторинг:
- Speed Insights в production
- Lighthouse в CI
- Регулярный аудит Web Vitals
Итог:
Достижение 90+ в Lighthouse требует системного подхода:
- 🖼️Оптимизация медиа (60% веса страницы)
- 📦Контроль бандла (дешевый JavaScript)
- ⚡Интеллектуальное кеширование
- 📊Непрерывный мониторинг
Стартовый пакет
bash
# Установите инструменты
npm install next bundle-analyzer @vercel/speed-insightsНастройте за 10 минут:
next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
});
module.exports = withBundleAnalyzer({
images: {
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 86400,
},
experimental: {
optimizePackageImports: ['lodash-es', 'date-fns'],
},
});