Назад в блог

~ 12 минут

Как ускорить Next.js-приложение: кеширование, изображения, API и Core Web Vitals

49

25.05.2026

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

Вадим Пашаев

Вадим Пашаев

Инженер, веб-разработчик, путешественник

Как ускорить Next.js-приложение: кеширование, изображения, API и 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 - не прыгает ли интерфейс во время загрузки.

Ориентиры такие:

Ориентиры Core Web Vitals для Next.js-сайта: LCP до 2.5 секунд, INP до 200 миллисекунд и CLS до 0.1

Важный момент: смотреть нужно не только 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 все равно будет плохим.

Для важной картинки первого экрана я бы дела