Кеширование в Next.js
Кеширование в Next.js это одна из самых сложных и часто задаваемых тем на собеседованиях. Next.js использует четыре уровня кеширования, каждый из которых работает на своем этапе.
Четыре уровня кеша
Request Memoization (сервер, во время рендеринга)
Дедупликация одинаковых fetch-запросов в рамках одного рендер-прохода. Если несколько компонентов запрашивают одни и те же данные, запрос выполнится один раз.
Data Cache (сервер, между запросами)
Кеширует результат fetch-запросов между пользовательскими запросами и деплоями. Работает как CDN для данных.
Full Route Cache (сервер, между запросами)
Кеширует отрендеренный HTML и RSC Payload статических роутов на этапе сборки.
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