Урок 06

Оптимизация продакшена

Next.js даёт много инструментов. Но нужны правильные настройки.

Bundle size

Проверь:

ANALYZE=true npm run build

Что делать:

  • Динамические импорты для тяжёлых компонентов
  • optimizePackageImports для библиотек иконок
  • Заменить тяжёлые зависимости на лёгкие
const Chart = dynamic(() => import('@/components/Chart'), {
    loading: () => <Skeleton />,
    ssr: false,
});
const nextConfig = {
    experimental: {
        optimizePackageImports: ['lucide-react', 'date-fns'],
    },
};

Изображения

Используй next/image:

import Image from 'next/image';

<Image
    src="/hero.jpg"
    alt="Hero"
    width={1200}
    height={600}
    priority // для первого экрана
    sizes="(max-width: 768px) 100vw, 1200px"
/>

Плюсы:

  • Конвертация в WebP/AVIF
  • Ресайз под экран
  • Ленивая загрузка
  • Предотвращение layout shift

Шрифты

import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin', 'cyrillic'], display: 'swap' });

Self-hosting, без запросов к Google.

Static vs Dynamic

Статика быстрее. Проверь, какие страницы можно сделать статическими:

export const dynamic = 'force-static';

Или ISR:

export const revalidate = 3600;

Кеш fetch

// Динамические данные
fetch(url, { cache: 'no-store' });

// Статика
fetch(url, { cache: 'force-cache' });

// ISR
fetch(url, { next: { revalidate: 60 } });

CDN

Vercel кеширует статику автоматически. На своём сервере — настрой CDN:

  • Cloudflare — бесплатно
  • Fastly, Bunny — для больших проектов

Кешируй:

  • /_next/static/* — навсегда
  • /public/* — на день-месяц
  • HTML — коротко, по стратегии

Сжатие

Vercel использует Brotli автоматически. На своём сервере:

gzip on;
gzip_types text/plain text/css application/json application/javascript;

brotli on;
brotli_types text/plain text/css application/json application/javascript;

Кеш в Nginx

location /_next/static/ {
    proxy_pass http://localhost:3000;
    proxy_cache_valid 200 365d;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

location /public/ {
    proxy_pass http://localhost:3000;
    proxy_cache_valid 200 30d;
}

Preload критичных ресурсов

<Head>
    <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
</Head>

Suspense и Streaming

Медленные части — в <Suspense>:

<Suspense fallback={<Skeleton />}>
    <SlowComponent />
</Suspense>

Пользователь видит оболочку сразу.

Core Web Vitals

  • LCP — самый большой контент. Оптимизируй Image с priority.
  • INP — отклик на действие. Убирай блокирующий JS.
  • CLS — сдвиг layout. Задавай размеры картинок, шрифты с display: swap.

Проверь:

  • PageSpeed Insights
  • Lighthouse
  • Vercel Speed Insights

Кеш данных

Кешируй тяжёлые запросы:

import { unstable_cache } from 'next/cache';

export const getPosts = unstable_cache(
    async () => db.post.findMany(),
    ['posts'],
    { revalidate: 60, tags: ['posts'] }
);

Логи и метрики

  • Sentry — ошибки
  • Vercel Analytics — посещаемость
  • Speed Insights — Core Web Vitals
  • Axiom, Logtail — логи

Database connection pooling

Serverless-функции создают много соединений. Решения:

  • Prisma Accelerate
  • PgBouncer
  • Neon, Supabase — serverless-базы
DATABASE_URL=postgresql://...?pgbouncer=true&connection_limit=1

Итоги

  • Анализ бандла
  • next/image и next/font
  • Статика где можно
  • CDN и кеш
  • Сжатие Brotli
  • Suspense и Streaming
  • Core Web Vitals
  • Connection pooling