Кеширование в Next.js

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

Четыре уровня кеша

1

Request Memoization (сервер, во время рендеринга)

Дедупликация одинаковых fetch-запросов в рамках одного рендер-прохода. Если несколько компонентов запрашивают одни и те же данные, запрос выполнится один раз.

2

Data Cache (сервер, между запросами)

Кеширует результат fetch-запросов между пользовательскими запросами и деплоями. Работает как CDN для данных.

3

Full Route Cache (сервер, между запросами)

Кеширует отрендеренный HTML и RSC Payload статических роутов на этапе сборки.

4

Router Cache (клиент, во время сессии)

Кеширует RSC Payload посещенных роутов в браузере. Навигация назад происходит мгновенно.

Request Memoization

React автоматически мемоизирует fetch-запросы с одинаковыми URL и параметрами:

// Оба компонента вызывают один и тот же fetch
// Запрос выполнится только один раз
async function Header() {
  const user = await fetch('/api/user').then(r => r.json())
  return <div>{user.name}</div>
}

async function Sidebar() {
  const user = await fetch('/api/user').then(r => r.json())
  return <div>{user.avatar}</div>
}

Это работает только во время одного рендер-прохода на сервере. Для ORM-запросов (Prisma, Drizzle) нужно использовать React.cache:

import { cache } from 'react'
import { db } from '@/lib/db'

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } })
})

Data Cache

По умолчанию fetch в Next.js кеширует результат в Data Cache:

// Кешируется навсегда (до ревалидации)
const res = await fetch('https://api.hackfrontend.com/docs')

// Ревалидация через 5 минут
const res = await fetch('https://api.hackfrontend.com/problems', {
  next: { revalidate: 300 }
})

// Без кеширования
const res = await fetch('https://api.hackfrontend.com/feed', {
  cache: 'no-store'
})

Ревалидация

Два способа обновить Data Cache:

По времени: данные считаются устаревшими через N секунд

const res = await fetch(url, { next: { revalidate: 60 } })

По событию: явная инвалидация через теги

// При загрузке
const res = await fetch(url, { next: { tags: ['problems'] } })

// При обновлении данных (в Server Action)
import { revalidateTag } from 'next/cache'
revalidateTag('problems')

Full Route Cache

Статические роуты кешируются целиком при сборке. Этот кеш включает HTML и RSC Payload.

Роут считается статическим, если:

  • Не использует динамические функции (cookies(), headers(), searchParams)
  • Все fetch-запросы кешированы

Роут считается динамическим, если:

  • Использует cookies(), headers() или searchParams
  • Есть fetch с cache: 'no-store'
  • Задан export const dynamic = 'force-dynamic'

Router Cache

Браузер кеширует RSC Payload посещенных страниц. Это позволяет:

  • Мгновенно возвращаться на предыдущие страницы
  • Prefetch'ить страницы при наведении на Link
import Link from 'next/link'

// Next.js автоматически prefetch'ит эту страницу
<Link href="/problems">Задачи</Link>

// Можно отключить prefetch
<Link href="/problems" prefetch={false}>Задачи</Link>

На собеседовании:

Router Cache часто вызывает путаницу. Страница может показывать устаревшие данные после мутации, потому что клиентский кеш еще не обновился. Решение: вызвать router.refresh() или revalidatePath() в Server Action.

Отключение кеширования

Если нужно полностью отключить кеш для сегмента:

// app/dashboard/layout.tsx
export const dynamic = 'force-dynamic'
export const revalidate = 0

Полезные ресурсы

Связанные темы