Оптимизация 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.

Оптимизация Next.js

Почему 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.5snext/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.jsdate-fns (67% меньше)
  • Lodashlodash-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 }
    }
  }
}

Результаты оптимизации (реальный кейс)

МетрикаДоПослеУлучшение
LCP2.8s0.8s⚡ 71%
CLS0.350.02🎯 94%
TBT420ms90ms🚀 78%
Размер страницы1.9 MB540 KB📦 71%
Lighthouse (моб)5898+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'],
  },
});