Назад в блог

~ 12 минут

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

5

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 все равно будет плохим.

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

import Image from 'next/image'

export function Hero() {
  return (
    <Image
      src="/hero.webp"
      alt="Сайт компании на Next.js"
      width={1440}
      height={720}
      priority
      sizes="100vw"
    />
  )
}

На что здесь обратить внимание:

  • width и height помогают заранее выделить место под картинку;
  • priority говорит Next.js, что изображение важно для первого экрана;
  • sizes помогает не отправлять мобильному устройству огромную desktop-картинку;
  • alt нужен для доступности и нормальной структуры страницы.

При этом priority не нужно ставить всем изображениям подряд. Если все важное, значит важного ничего нет.

Для картинок ниже первого экрана лучше оставить обычную ленивую загрузку.

Еще один момент: если основная картинка первого экрана вставлена через CSS background, браузер может обнаружить ее позже.

.hero {
  background-image: url('/hero-large.jpg');
}

Для декоративного фона это нормально. Для LCP-изображения чаще лучше использовать обычный Image, чтобы браузер увидел ресурс раньше.

JavaScript: меньше работы на клиенте

Next.js хорош тем, что часть работы можно оставить на сервере.

Но если почти каждый компонент помечен как 'use client', приложение начинает превращаться в тяжелый SPA: больше JavaScript, больше hydration, больше работы на главном потоке, хуже INP.

Я бы держал простое правило:

Клиентским должен быть только тот компонент,
которому реально нужны state, effects, browser API или обработчики событий.

Обычно не обязаны быть клиентскими:

  • статья;
  • блок услуг;
  • список преимуществ;
  • карточка кейса;
  • футер;
  • хлебные крошки;
  • SEO-текст;
  • статичная часть карточки товара.

А вот клиентскими чаще будут:

  • формы;
  • фильтры;
  • модальные окна;
  • калькуляторы;
  • корзина;
  • личный кабинет;
  • интерактивные карты;
  • элементы, завязанные на browser API.

Если есть тяжелый компонент, который нужен не сразу, его можно грузить динамически:

import dynamic from 'next/dynamic'

const PriceCalculator = dynamic(() => import('./PriceCalculator'), {
  loading: () => <p>Загрузка...</p>,
})

Если тяжелая библиотека нужна только после действия пользователя, ее можно импортировать прямо в обработчике:

async function handleSearch(value) {
  const Fuse = (await import('fuse.js')).default
  const fuse = new Fuse(items)
  return fuse.search(value)
}

Так библиотека не попадет в начальный бандл просто потому, что где-то на странице есть поиск.

Для проверки бандлов можно использовать analyzer:

ANALYZE=true npm run build

Задача не в том, чтобы посмотреть красивую диаграмму и забыть. Задача - понять, какие зависимости реально попали на клиент, почему они там оказались и можно ли их убрать, разделить или загрузить позже.

Шрифты и сторонние скрипты

Шрифты тоже могут портить скорость.

Особенно если подключить несколько семейств, много начертаний и внешние запросы к font-сервисам. В итоге текст сначала появляется одним шрифтом, потом меняется, layout двигается, CLS растет.

В Next.js лучше использовать next/font:

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

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

Но даже с next/font не стоит тащить все подряд:

  • не подключайте 5 семейств без необходимости;
  • не берите все веса от 100 до 900;
  • используйте variable font, если он подходит;
  • проверяйте, не скачет ли текст после загрузки.

Со сторонними скриптами история похожая.

Аналитика, пиксели, CRM-виджеты, карты, онлайн-чаты, A/B-тесты - все это может быть полезно для бизнеса. Но если загрузить все сразу на первом экране, пользователь будет платить за это скоростью.

Для скриптов в Next.js есть next/script, где можно управлять стратегией загрузки:

import Script from 'next/script'

export function AnalyticsScript() {
  return (
    <Script
      src="https://example.com/analytics.js"
      strategy="afterInteractive"
    />
  )
}

А тяжелый чат-виджет иногда лучше показывать после действия пользователя или с задержкой, чем грузить его сразу на каждой странице.

Практический порядок оптимизации

Если бы ко мне пришел проект со словами "Next.js тормозит", я бы не начинал с переписывания компонентов.

Сначала прошелся бы по такой схеме.

1. Проверить TTFB

Если сервер долго отдает первый байт, нужно смотреть рендеринг, CMS, API, базу, инфраструктуру и кеширование.

2. Найти LCP-элемент

Если это изображение - проверяем размер, формат, priority, sizes, CDN, способ вставки и время обнаружения ресурса.

Если это текст - смотрим шрифты, CSS, TTFB и блокирующие ресурсы.

3. Разобрать API

Сколько запросов делает страница? Какие самые медленные? Не тянем ли лишние поля? Есть ли пагинация? Можно ли кешировать ответ?

4. Разделить статическое и динамическое

Не нужно делать всю страницу динамической из-за одного блока. Статическую основу можно кешировать, а персональные или часто меняющиеся данные получать отдельно.

5. Уменьшить клиентский JavaScript

Проверить use client, тяжелые библиотеки, динамические импорты, карты, графики, слайдеры, редакторы, виджеты.

6. Убрать CLS

У изображений должны быть размеры. У iframe, рекламных и динамических блоков - зарезервированное место. Шрифты не должны резко менять layout.

7. Настроить мониторинг

Разовая оптимизация быстро устаревает. Новый виджет, редизайн, обновление CMS или добавление каталога могут снова просадить скорость.

Что обычно дает быстрый эффект

На практике быстрее всего часто помогают:

  • кеширование страниц и данных, которые не обязаны быть динамическими;
  • исправление LCP-картинки первого экрана;
  • уменьшение лишних данных из CMS;
  • перенос части компонентов из client в server;
  • удаление тяжелых зависимостей из начального бандла;
  • отложенная загрузка сторонних скриптов;
  • нормальные размеры для изображений и динамических блоков;
  • упрощение инфраструктуры, если запрос проходит через лишние слои.

Но здесь важно не обмануться.

Если сайт иногда отвечает за 1 секунду, а иногда за 12, проблема может быть не в изображениях. Может быть конфликт зависимостей, нестабильная CMS, тяжелый запрос к базе, перегруженный сервер или лишний proxy-слой.

Именно поэтому в Berhasm мы сначала убирали корневую нестабильность, а уже потом можно было спокойно идти в кеширование, каталог, изображения и Core Web Vitals.

Где границы оптимизации

Не каждый сайт обязан иметь 100/100 в Lighthouse.

Для бизнеса важнее другое:

  • страницы стабильно открываются;
  • пользователь быстро видит основной контент;
  • формы, фильтры и кнопки не тормозят;
  • поисковые роботы нормально обходят сайт;
  • команда понимает, где узкие места;
  • после каждого релиза скорость не разваливается заново.

Иногда попытка добить синтетический балл до идеала превращается в отдельную бесконечную задачу.

Можно потратить много часов, получить плюс 2 балла в Lighthouse и почти ничего не изменить для реальных пользователей.

Я бы смотрел на конкретные страницы и сценарии:

  • главная;
  • страницы услуг;
  • статьи;
  • категории каталога;
  • карточки товаров;
  • формы заявок;
  • поиск и фильтры.

И отдельно проверял mobile. Именно там чаще всего всплывает тяжелый JavaScript, плохой LCP и неприятный INP.

Полезные источники

Вывод

Ускорение Next.js-приложения - это не один трюк.

Сначала нужно понять, где пользователь реально ждет: сервер, CMS, API, изображения, JavaScript, шрифты, сторонние скрипты или инфраструктура.

И уже после этого выбирать инструмент: кеширование, revalidation, оптимизацию картинок, сокращение данных из CMS, динамические импорты, перенос компонентов на сервер или полноценный технический аудит.

Next.js дает много возможностей для скорости. Но фреймворк не спасает автоматически, если проект каждый раз делает лишнюю работу, тащит огромные ответы из CMS, грузит тяжелый JavaScript и не разделяет статическое от динамического.

Поэтому главный вопрос не "как получить 100 в Lighthouse".

Главный вопрос проще:

Где именно пользователь ждет, и какую работу сайт делает зря?

Когда это понятно, оптимизация перестает быть гаданием и становится нормальной инженерной задачей.

Читать далее

Что такое Next.js и для чего он нужен? | PXSTUDIO
Что такое Next.js и для чего он нужен?

Next.js - это фреймворк, основанный на React, который позволяет создавать веб-приложения с улучшенной производительностью и улучше...

Получение данных в Next.js | PXSTUDIO
Получение данных в Next.js

В прошлой статье мы вкратце упомянули, что в Next.js есть две формы пререндеринга: статическая генерация (SSG) и генерация на стор...

Ускорение сайта на Next.js и Payload: как мы сократили время загрузки в 2 раза | PXSTUDIO
Ускорение сайта на Next.js и Payload: как мы сократили время загрузки в 2 раза

Кейс Berhasm.com: сайт на Next.js и Payload CMS тормозил, падал и плохо индексировался. Рассказываем, что нашли в архитектуре, как...

Подписаться на рассылку

Получите интересные новости по веб-разработке и AI

Этот сайт защищен reCAPTCHA, применяются Политика конфиденциальности и Условия использования Google.

Подписаться на рассылку

Получите интересные новости по веб-разработке и AI

Этот сайт защищен reCAPTCHA, применяются Политика конфиденциальности и Условия использования Google.

Расскажите, что нужно сделать

Разберем задачу и предложим следующий шаг

Contact to pxstudio

Сайт, сервис, Telegram-бот, AI-интеграция или оптимизация текущего проекта — опишите ситуацию, а мы подскажем нормальный технический путь.

Этот сайт защищен reCAPTCHA, применяются Политика конфиденциальности и Условия использования Google.

Есть интересная идея?

И вы очень хотите ее реализовать, пишите нам и получите подробное коммерческое предложение и быструю реализацию