Журнал / Разработка веб-продуктов

Как работает кэширование в Next.js v13+ с помощью App Router

Как работает кэширование в Next.js v13+ с помощью App Router

Подробный обзор того, как работает кэширование в Next.js 13+, какие типы кеша существуют и как это влияет на производительность и SEO.

Что такое кеш и как он работает в Next.js

Кеш - это способ хранения результатов вычислений или запросов, чтобы вам не приходилось их повторять, ускоряя работу вашего приложения. В Next.js можно кешировать HTML-страницы, данные, запросы, рендеринг компонентов и даже целые маршруты.

С появлением App Router (начиная с Next.js 13) философия изменилась: теперь кэширование включено по умолчанию, и это зависит от разработчика, чтобы отключить его там, где требуется динамическое поведение.

Политика Next.js состоит в следующем:

"Мы кешируем всё, если не сказано обратное"

Если вы явно не указали cache: 'no-store', next: { revalidate }, или dynamic = 'force-dynamic', то:

  • fetch() будет кешироваться
  • макет и страница также могут быть кешированы
  • маршруты будут добавлены в кеш маршрутов
  • рендеринг страницы попадет в Полный кеш маршрутов

Это обеспечивает производительность, но может привести к устаревшим данным и затруднениям в отладке.

Типы кэширования в Next.js

Источник: Документация Next.js

Полный кеш маршрутов (кэширование маршрутов с сервера)

Используется с ISR (Инкрементальная статическая регенерация).

  • Вся страница кешируется (HTML, данные из layout.tsx, page.tsx, fetch(), headers(), cookies()).
  • Кеш находится на сервере.
  • Работает по умолчанию, если отсутствует динамическое поведение.
  • Инвалидируется через revalidate или revalidateTag().

Лучше всего подходит для блогов, маркетинговых страниц и другого редко изменяющегося контента.

Пример:

export const revalidate = 60; // regenerate the page every 60 seconds

Кеш данных (кэширование запросов с сервера)

  • Ответы от fetch() в компонентах сервера кешируются.
  • Контролируется с помощью next: { revalidate } или cache: 'no-store'.

Пример:

await fetch('https://api.com/posts', {
  next: { revalidate: 300 },
});

Мемоизация запроса (кэширование памяти на запрос)

Это оптимизация внутри Next.js: один и тот же fetch() или функция с одинаковыми аргументами вызывается только один раз за рендеринг.

  • В пределах одного серверного рендера Next.js отслеживает идентичные запросы.
  • Если они уже были вызваны, результат берется из кэша в памяти.
  • Работает независимо от cache: 'force-cache' или no-store.

Подходит для:

  • DRY (не повторяйся), когда запрашиваются одни и те же данные в разных компонентах.
  • Улучшения производительности без ручной оптимизации.
  • Когда используется тот же fetch как в layout.tsx так и в page.tsx.

Не подходит для:

  • Переиспользования между различными запросами или пользователями (не глобальный кеш).
  • Обход кеша запросов — это работает поверх Мемоизации запросов.
// lib/getUser.ts
export async function getUser() {
  return fetch('https://api.com/user').then(res => res.json());
}

// app/layout.tsx
export default async function Layout({ children }) {
  const user = await getUser(); // Call 1
  return (
    <div>
      <Sidebar user={user} />
      {children}
    </div>
  );
}

// app/page.tsx
import { getUser } from '../lib/getUser';

export default async function Page() {
  const user = await getUser(); // Call 2, but actually not repeated
  return <p>Hello, {user.name}</p>;
}

Физически, fetch будет вызван только один раз, и результат будет использоваться в обоих местах.

Кеш маршрутизатора (клиентский кеш)

Это кеш, который вы контролируете сами, используя библиотеки типа SWR, React Query и т. д.

  • Работает только с клиентской навигацией (<Link>, router.push()).
  • Next.js не контролирует этот кеш - вы сами решаете, как его кешировать.
  • Очень быстро, потому что ничего не перерисовывается - React просто монтируется из памяти.
  • Но он существует только до тех пор, пока пользователь не перезагрузит страницу (F5).

Лучше всего подходит для интерактивных компонентов, форм, панелей управления.

Пример:

'use client';
import useSWR from 'swr';

const { data } = useSWR('/api/data', fetcher);

Визуальное сравнение типов кэширования

Тип кэшированияГде это работаетУправлениеПример
Полный кеш маршрутовСервер (весь маршрут)перепроверятьтэгexport const revalidate = 60
Кэш данныхСервер (запрос)кэшперепроверятьfetch(..., { next: ... })
Кэш статической генерацииСервер (время сборки)НетJSX без данных
Клиентский кэшБраузер / клиентВыSWR / React Query

Реальный пример

Предположим, вы строите магазин:

СтраницаЧто кэшироватьКакой тип кэша использовать
/productsСписок продуктов, обновляется каждые 5 минутКэш полного маршрута (ISR)
/api/productsДанные API продуктаКэш данных (с тегом перепроверки)
Навигация SPAБыстрая навигацияКэш маршрутизатора (клиентский кэш)

Общие проблемы кэширования в App Router

Обновленные данные — но сайт отображает старые данные

fetch() использует force-cache по умолчанию, если не указано cache: 'no-store' или next: { revalidate }. В результате Next.js кэширует данные и никогда их не обновляет.

Решение:

  • Добавьте cache: 'no-store' для отключения кэширования
  • Или next: { revalidate: 60 } для ISR
fetch('https://api.com/data', { next: { revalidate: 60 } });

URL (searchParams) изменился, но данные остались теми же

Next.js не перерисовывает запрос fetch, если URL остается тем же. Если используете параметры, добавьте их непосредственно к URL запроса fetch:

fetch(`https://api.com/posts?category=${searchParams.category}`, {
  next: { revalidate: 60 }
});

Переход через <Link>, но данные не обновились

Кэш маршрутизатора (клиентский кэш) был использован. Next.js не делает новый запрос при возвращении на страницу.

Решение:

  • Принудительное обновление данных через router.refresh()
  • Используйте cache: 'no-store' если данные всегда должны быть свежими

revalidatePath() или revalidateTag() не работают

  • Кэш не был создан (например, из-за no-store)
  • Вы не используете теги в fetch
  • Вы вызываете revalidatePath() вне серверного действия

Решение:

  • Убедитесь, что fetch использует next: { tags: [...] }
  • Вызовите revalidateTag('tag') в серверной функции

Макет кэшируется и не обновляется

layout.tsx также кэшируется в качестве RSC с перепроверкой, и если данные внутри кэшированы, они не обновятся до следующего ISR.

Решение:

  • Используйте cache: 'no-store' для fetch в layout.tsx, если, например, он содержит профиль пользователя
  • Или переместите fetch из макета на страницу (или клиентский компонент с SWR)

Неожиданное поведение в режиме разработки (dev)

В next dev, многие кэши отключены или работают по-другому, и перепроверка не работает надежно в режиме dev.

Совет:

Тестировать кеш с следующей сборкой && следующим запуском или предпросмотром Vercel.

Комбинация cookies(), headers(), params, и fetch() нарушает кеш.

Когда вы используете динамические вещи, такие как:

cookies()

headers()

useSearchParams()

generateMetadata() с динамическим

Next.js автоматически отключает кеш страниц и переключается на dynamic = 'force-dynamic'. Чеклист: как избежать проблем с кешем

Всегда явно устанавливайте поведение fetch:

  • Не забудьте включить параметры запроса непосредственно в URL запроса
  // no cache at all
  fetch(..., { cache: 'no-store' })

  // ISR (revalidate every 60 seconds)
  fetch(..., { next: { revalidate: 60 } })

  // manual invalidation via tag
  fetch(..., { next: { tags: ['products'] } })
  • Тестирование с
  • следующей сборкой && следующим запуском — в противном случае у вас может возникнуть ложное чувство уверенности. Если вам нужно вручную инвалидировать данные, используйте
  • revalidatePath()илиrevalidateTag(). Как работает кеширование в Next.js v13+ с App Router