Отключил одну настройку в Next.js 16 — PageSpeed вырос на 26 баллов. Разбираю конфиг, который ChatGPT не посоветует

Я делаю сайты на Next.js 16 с App Router. За последний год прогнал через PageSpeed десятки сборок — своих и клиентских. Выработал конфигурацию, которая стабильно даёт высокие баллы. Но путь к ней был не прямой: половина «оптимизаций» из документации и советов ChatGPT на практике либо ничего не даёт, либо ломает вещи.

Ниже — мой рабочий конфиг, подводные камни и компонент для медиа, который дал не меньший прирост, чем весь next.config.js вместе взятый.

Два конфига: мой рабочий vs «рекомендуемый»

Вот что использую я:

const nextConfig = { reactStrictMode: true, poweredByHeader: false, compress: true, // в проде false — сжатие на nginx experimental: { inlineCss: true, turbopackFileSystemCacheForDev: true, optimizePackageImports: [ "swiper", "@iconify/react", "react-markdown", "remark-gfm", "rehype-raw", "isomorphic-dompurify", "@react-spring/web", ], }, images: { formats: ["image/avif", "image/webp"], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], qualities: [70, 75, 80], minimumCacheTTL: 14400, }, };
Показатели PageSpeed Insights c моим next.config.js
Показатели PageSpeed Insights c моим next.config.js

А вот что советует ChatGPT (и большинство статей):

const nextConfig = { reactStrictMode: true, poweredByHeader: false, compress: true, reactCompiler: true, // ← вот тут начинаются проблемы experimental: { optimizeCss: true, inlineCss: false, turbopackFileSystemCacheForDev: true, optimizePackageImports: [ /* ... */ ], }, images: { /* ... */ }, };
Показатели PageSpeed Insights c рекомендуемым next.config.js
Показатели PageSpeed Insights c рекомендуемым next.config.js

Разница в трёх строчках. Но эти три строчки — минус 26 баллов в PageSpeed.

Что включаю и зачем — коротко

poweredByHeader: false — убирает заголовок X-Powered-By: Next.js. Не влияет на скорость, но зачем показывать стек всему интернету.

optimizePackageImports — реально полезная штука. Next.js импортирует только используемые части библиотеки. Для @iconify/react (сотни иконок в пакете) и swiper разница в размере бандла — десятки килобайт.

Форматы изображений: AVIF первым. ["image/avif", "image/webp"] — порядок важен. Next.js отдаёт первый поддерживаемый формат. AVIF сжимает лучше WebP при том же визуальном качестве.

minimumCacheTTL: 14400 — кеш оптимизированных изображений на 4 часа. На проде можно ставить 86400 (сутки) и выше.

Ловушка с inlineCss: true — прощайте, OG-картинки

На эту граблю я потратил несколько часов.

inlineCss: true встраивает весь CSS прямо в HTML. Убирает отдельные запросы на стили. Звучит как чистый выигрыш. И формально — да, рендер быстрее.

Но. Если CSS много, <head> раздувается настолько, что парсеры соцсетей не доходят до мета-тегов. Они обычно читают первые ~50 КБ HTML — а ваш og:image оказывается где-то на 80-м килобайте, похоронённый под инлайн-стилями.

<meta property="og:image" content="https://yoursite.ru/og-image.jpg"> <!-- ↑ парсер Telegram/VK/ОК сюда просто не дочитывает -->

Кидаете ссылку в Telegram — вместо красивой карточки с картинкой голый URL. В VK и ОК — то же самое.

Моё решение: я осознанно оставил inlineCss: true. Для моих проектов приоритет — Core Web Vitals и скорость загрузки. Прирост от инлайн-CSS перевешивает потерю OG-превью.

Но если для вас важна красивая карточка при шеринге (а для контентных сайтов и интернет-магазинов это критично) — выключайте inlineCss. Разница в скорости будет минимальная, а превью вернутся.

React Compiler: выключил — и сайт стал быстрее

Это самое контринтуитивное.

В Next.js 16 есть reactCompiler: true — интеграция с новым React Compiler. Он автоматически мемоизирует компоненты и должен ускорять рендер. Все советуют включить. ChatGPT советует включить.

Я выключил:

reactCompiler: false,

PageSpeed вырос на 26 баллов. Не на одном проекте — на нескольких.

Почему так? Моя гипотеза: если у вас SSR-first архитектура (а так и нужно делать в App Router), клиентских компонентов мало. React Compiler добавляет обвязку для мемоизации на каждый клиентский компонент — и этот оверхед перевешивает выигрыш, потому что мемоизировать особо нечего.

Компилятор не плохой. Он для приложений с тяжёлым клиентским деревом — дашборды, SPA. Но если у вас маркетинговый сайт или корпоративник на серверных компонентах — тестируйте без него. Результат может удивить.

Серверные компоненты — главная оптимизация (важнее конфига)

Самая частая ошибка, которую я вижу в чужих проектах: разработчик ставит "use client" на корневой layout или на крупную обёртку — и всё поддерево становится клиентским. Весь React этих компонентов летит в браузер.

Мой подход простой:

Серверное по умолчанию — страницы, секции, карточки, хедер (кроме бургер-меню). Ноль байт JS на клиенте.

Клиентское — только островки интерактивности: формы, анимации, свайперы, модалки, карты. Маленькие "use client" компоненты, встроенные в серверную обёртку.

Меньше JS → быстрее загрузка → выше PageSpeed → лучше поведенческие → лучше SEO. Цепочка прямая.

Компонент для медиа — где конфиг заканчивается и начинается архитектура

Вот о чём почти никто не пишет. Можно идеально настроить next.config.js — но если медиа-контент обрабатывается неправильно, сайт всё равно будет тормозить.

Я написал серверный компонент ComponentTypeMedia, который решает это системно. Принцип — изображения рендерятся на сервере (0 байт JS на клиенте), видео и аудио — клиентские островки.

Как это работает:

Изображения и GIF — полностью серверные. Компонент определяет тип файла и для картинок использует стандартный next/image без единой строки клиентского кода. Blur-placeholder для плавной загрузки, автоматический AVIF/WebP (из конфига), адаптивные sizes:

<Image src={src} width={mediaWidth} height={mediaHeight} alt={mediaAlt} priority={isPriority} placeholder={!isGif && !isPriority ? "blur" : "empty"} sizes={sizes ?? "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"} unoptimized={isGif} // GIF не прогоняем через оптимизатор fetchPriority={isPriority ? "high" : "auto"} decoding={isPriority ? "sync" : "async"} />

Обратите внимание на fetchPriority и decoding — для первого экрана (hero-баннер) ставим high и sync, для остального — auto и async. Браузер сам решает, что грузить первым, а что подождёт.

Видео — ленивый клиентский остров. Видео по своей природе требует JS (play/pause, poster, контролы). Поэтому MediaVideoClient — это отдельный "use client" компонент. Серверный родитель только подготавливает данные (URL, poster, размеры) и передаёт вниз.

Тот же подход для аудио — MediaAudioClient как отдельный клиентский островок.

Зачем WebM-фоллбэк. Компонент автоматически формирует WebM-альтернативу рядом с MP4:

const webmSrc = src.endsWith(".mp4") ? replaceExt(src, ".webm") : null;

WebM весит заметно меньше MP4 при том же качестве. Если бэкенд конвертирует видео в оба формата — браузер выберет лёгкий.

Почему это даёт прирост. На странице с 10 изображениями и 2 видео — без такого подхода весь медиа-компонент клиентский. Это значит: React-код компонента, хуки, обработчики — всё летит в бандл. С серверным подходом 10 изображений — это 0 байт JS. Клиентский код только у двух видео. На тяжёлых страницах (портфолио, каталоги) разница — десятки килобайт бандла.

Заголовки безопасности — бонус к SEO

Не про скорость, но важно:

async headers() { return [{ source: "/:path*", headers: [ { key: "X-DNS-Prefetch-Control", value: "on" }, { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" }, { key: "X-XSS-Protection", value: "1; mode=block" }, { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), interest-cohort=()" }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, ], }]; }

HSTS с preload — обязательно для HTTPS-сайтов. interest-cohort=() — отключает FLoC. Это не про скорость — это про то, чтобы поисковики видели технически грамотный сайт. Lighthouse учитывает заголовки безопасности в разделе Best Practices.

Итого: что реально двигает метрики

Три вещи, в порядке влияния:

1. Серверные компоненты везде, где можно. Это не настройка — это архитектура. Но даёт больше всего. Каждый "use client", который вы убрали — это минус JS в бандле.

2. Правильная обработка медиа. Изображения — серверные с next/image, blur-placeholder, адаптивные sizes. Видео и аудио — клиентские островки. Не один большой «универсальный» клиентский компонент, а разделение по типу контента.

3. Конфиг с тестированием, а не по советам. reactCompiler: false дал +26 баллов. inlineCss: true дал прирост, но убил OG-превью. Каждую экспериментальную фичу нужно замерять на своём проекте, а не включать потому что «все советуют».

Не доверяйте конфигам из статей (включая эту). Замеряйте на своём проекте. PageSpeed, WebPageTest, реальные метрики из CrUX — только они покажут правду.

Если есть вопросы по настройке или хотите показать свой конфиг — пишите в комментариях, разберём.

4 комментария