~ 12 минут
Как ускорить Next.js-приложение: кеширование, изображения, API и Core Web Vitals
49
25.05.2026
Практический разбор оптимизации Next.js-приложения: где искать узкие места, как настроить кеширование, ускорить изображения, API-запросы, JavaScript и улучшить Core Web Vitals без косметических правок


Содержание
У медленных сайтов есть одна неприятная особенность. Снаружи все выглядит очень просто, сайт просто не открывается. А внутри может быть вообще что угодно: страница каждый раз рендерится на сервере, CMS долго отвечает, API тащит лишние данные, hero-картинка грузится слишком поздно, клиентский JavaScript раздувает бандл, а сверху еще лежит аналитика, чат, карта и пара виджетов "на всякий случай".
И вот тут легко попасть в ловушку. Открываешь Lighthouse, видишь красные цифры, находишь большую картинку и думаешь: ну все, сейчас сожмем изображения и сайт полетит.
Иногда действительно полетит. Но часто нет.
Потому что картинка может быть не причиной, а просто самым заметным симптомом. А настоящая проблема сидит глубже: в рендеринге, кешировании, API, базе данных, CMS или инфраструктуре.
Мы с этим столкнулись на реальном проекте Berhasm.com. Там сайт на Next.js и Payload CMS тормозил, периодически падал и мешал нормально двигаться в SEO. На первый взгляд можно было сразу идти в оптимизацию картинок, бандлов и Core Web Vitals. Но в процессе стало понятно, что сначала нужно убрать корневую нестабильность: конфликтующие версии Payload, лишние инфраструктурные слои и непредсказуемое поведение API. Подробно я разбирал это в кейсе ускорения сайта на Next.js и Payload CMS.
В этой статье давайте спокойно разложим, как я бы подходил к ускорению Next.js-приложения: что проверять первым, где помогает кеширование, как смотреть на изображения, API, JavaScript и Core Web Vitals.
Без магической кнопки "сделать быстро". Но с нормальной рабочей логикой.
Сначала нужно понять, где именно сайт ждет
Самая частая ошибка в оптимизации - чинить не тот слой.
У Next.js-приложения есть несколько мест, где может теряться время:
- сервер долго готовит HTML;
- CMS или backend медленно отдают данные;
- страница запрашивает слишком много полей;
- каталог или список товаров грузится без пагинации;
- важная картинка первого экрана скачивается поздно;
- клиентский JavaScript слишком тяжелый;
- почти все компоненты стали
use client; - сторонние скрипты забивают главный поток браузера;
- шрифты или изображения двигают layout после загрузки;
- CDN или reverse proxy настроены так, что кеш почти не помогает.
Поэтому я бы начинал не с правок, а с диагностики.
Минимально стоит посмотреть:
- PageSpeed Insights или Lighthouse - чтобы увидеть LCP, INP, CLS и тяжелые ресурсы;
- Chrome DevTools Performance - чтобы поймать long tasks и тяжелый JavaScript;
- Network tab - чтобы проверить TTFB, размеры файлов и порядок загрузки;
- серверные логи или мониторинг - чтобы понять, где тормозит backend;
- простой
curl- чтобы отделить ответ сервера от отрисовки в браузере.
Например:
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://example.com/catalog
Если TTFB уже большой, то проблема не только во frontend. Браузер еще не получил HTML, а пользователь уже ждет.
Про такие быстрые проверки через терминал я отдельно писал в статье про использование curl для замеров производительности. Это не самый красивый инструмент в мире, зато он быстро показывает, где примерно начинается проблема: на сервере или уже в браузере.
Если TTFB нормальный, но LCP плохой, тогда смотрим первый экран: изображения, CSS, шрифты, клиентский JavaScript, сторонние скрипты и порядок загрузки ресурсов.
Это звучит банально, но сильно экономит время. Потому что "медленный сайт" - это не диагноз. Это жалоба пользователя.
Core Web Vitals: что вообще нужно улучшать
Core Web Vitals - это не просто баллы в красивом отчете.
По сути, это три вопроса:
LCP- как быстро человек увидел основной контент;INP- насколько быстро страница реагирует на действия;CLS- не прыгает ли интерфейс во время загрузки.
Ориентиры такие:
Важный момент: смотреть нужно не только Lighthouse на своем ноутбуке. Google оценивает реальный пользовательский опыт, обычно по 75-му перцентилю и отдельно для mobile/desktop.
То есть если у вас на хорошем MacBook все быстро, это еще не значит, что сайт нормально работает у человека с обычного телефона и мобильного интернета.
Для Next.js это особенно заметно. На desktop страница может открываться прилично, а на mobile все начинает проседать из-за тяжелого JavaScript, большой hero-картинки или медленного API.
Кеширование: не заставляйте сервер делать одно и то же
Теперь про кеш.
В Next.js очень легко случайно сделать так, что страница каждый раз заново ходит в CMS, собирает данные, рендерит HTML и отдает пользователю результат. Для личного кабинета это нормально. Для статьи, страницы услуги или категории каталога - часто нет.
Если данные не персональные и не меняются каждую секунду, их почти всегда стоит кешировать или обновлять через revalidation.
Простой пример:
const res = await fetch('https://cms.example.com/api/services', { cache: 'force-cache', })
Если данные можно обновлять раз в час:
const res = await fetch('https://cms.example.com/api/articles', { next: { revalidate: 3600 }, })
Так пользователь не обязан каждый раз ждать CMS. Next.js может отдать кешированный результат, а данные обновятся по заданному правилу.
Для сайтов с CMS особенно удобно использовать теги:
const res = await fetch('https://cms.example.com/api/articles', { next: { tags: ['articles'] }, })
А после публикации новой статьи сбросить нужный кеш:
import { revalidateTag } from 'next/cache' export async function POST() { revalidateTag('articles') return Response.json({ ok: true }) }
Это хороший сценарий для блога, услуг, кейсов, FAQ, каталога и других разделов, где контент обновляется событием, а не каждую секунду.
Но тут есть нюанс.
В Next.js кеширование зависит от версии, App Router, настроек маршрута и того, используете ли вы cookies(), headers() и другие request-time API. Поэтому я бы не рассчитывал на то, что Next.js сам разберется. Лучше явно решить:
- какие данные кешируем;
- на сколько;
- что должно инвалидировать кеш;
- какие страницы всегда динамические;
- какие части можно разделить на статические и динамические.
Не нужно кешировать весь мир
Кеш - штука полезная, но довольно опасная, если включать его без понимания.
Не стоит делать статическими:
- личный кабинет;
- корзину;
- персональные цены;
- остатки товара, если они критичны;
- страницы с приватными данными;
- результаты, завязанные на пользователя, регион или cookies.
Но это не значит, что вся страница должна быть динамической.
Например, карточка товара может иметь кешируемую основу:
- название;
- описание;
- изображения;
- характеристики;
- SEO-данные.
А отдельно динамически можно получать:
- наличие;
- персональную цену;
- избранное;
- корзину;
- рекомендации.
Вот это, кажется, один из самых важных подходов в оптимизации Next.js: не превращать всю страницу в динамическую только из-за одного маленького блока.
API и CMS: не тащите лишнее
Следующий частый источник боли - API.
Особенно если сайт работает с headless CMS. Сама идея хорошая: контент живет отдельно, frontend отдельно, редакторы не лезут в код, сайт можно развивать спокойнее. Я отдельно разбирал этот подход на странице про headless CMS.
На старте очень хочется сделать один удобный запрос и получить, например, товар и все что с ними связано.
И CMS честно отдает все: товар, категорию, коллекции, галерею, SEO, похожие товары, локали, вложенные изображения, метаданные файлов, автора, дату обновления и еще кучу служебных полей.
Интерфейсу нужно 15 полей.
API возвращает 150.
Плохой вариант:
GET /api/products?populate=deep
Более здравый вариант:
GET /api/products?fields=title,slug,price&limit=24&page=1
Конкретные параметры зависят от CMS или backend, но принцип простой: страница должна получать ровно те данные, которые нужны для текущего экрана.
Для страницы категории обычно достаточно:
- название;
- slug;
- цена;
- превью;
- доступность;
- короткое описание;
- SEO-данные;
- пагинация.
Все остальное можно не тянуть сразу.
Еще я бы проверил:
- есть ли пагинация;
- нет ли N+1-запросов;
- не вызываете ли вы свой же Route Handler из Server Component;
- есть ли индексы в базе для фильтров и поиска;
- не считаются ли тяжелые агрегаты на каждый запрос;
- можно ли кешировать дорогие запросы к CMS.
В кейсе Berhasm это было особенно заметно: скорость Next.js-проекта складывается не только из React-компонентов. Если CMS и API ведут себя нестабильно, frontend не спасет ситуацию одной оптимизацией картинок.
Изображения: часто виноваты, но не всегда
Для корпоративных сайтов, блогов, лендингов и e-commerce LCP-элементом часто становится изображение.
Это может быть:
- hero на главной;
- баннер категории;
- большое изображение товара;
- обложка статьи;
- промо-блок услуги.
Next.js дает next/image, и это действительно полезный инструмент. Он помогает отдавать изображения подходящего размера, использовать современные форматы, lazy-load для картинок ниже первого экрана и уменьшать риск layout shift.
Но next/image - не волшебство.
Если hero весит 3 МБ, приходит из медленной CMS, вставлен как CSS background и обнаруживается браузером поздно, LCP все равно будет плохим.
Для важной картинки первого экрана я бы дела