Более умное кэширование в Next.js: частичный рендеринг и переиспользуемые компоненты
Вы создали приложение на Next.js с использованием App Router. Загрузка данных работает. Страницы отрисовываются. Но вы не уверены, правильна ли ваша стратегия кэширования — или есть ли она вообще. Вы видели, как неожиданно появляются устаревшие данные, наблюдали, как один и тот же запрос к базе данных выполняется несколько раз за один request, и задавались вопросом, почему некоторые маршруты работают медленно, несмотря на то что они «статические».
Эта статья объясняет, как на самом деле работает кэширование данных в Next.js App Router, как взаимодействуют три слоя кэша и как создавать переиспользуемые серверные компоненты, которые инкапсулируют как загрузку данных, так и политику кэширования. Мы также рассмотрим Next.js Partial Prerendering и то, как частичный рендеринг в React 19 позволяет применять стратегии кэширования на уровне компонентов.
Ключевые выводы
- Next.js использует три слоя кэша (Data Cache, Full Route Cache, Router Cache), которые каскадно взаимодействуют во время запросов
- Компоненты должны управлять своей политикой кэширования, используя
cache()из React для дедупликации и опции fetch для контроля времени жизни - Ревалидация на основе тегов позволяет точечно инвалидировать кэш на нескольких маршрутах
- Partial Prerendering (PPR) позволяет комбинировать статические оболочки с динамическим потоковым контентом через границы Suspense
Как работают три слоя кэша вместе
Кэширование в Next.js осуществляется через три различных механизма, которые взаимодействуют при каждом запросе.
Data Cache
Data Cache сохраняет результаты fetch между запросами и развертываниями. Когда вы вызываете fetch с включенным кэшированием, Next.js сохраняет ответ на стороне сервера. Последующие запросы возвращают кэшированные данные до тех пор, пока не произойдет ревалидация.
// Кэшируется на 1 час для всех запросов
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
})
Full Route Cache
Во время сборки Next.js отрисовывает статические маршруты в HTML и RSC payload. Этот Full Route Cache мгновенно отдает предварительно отрисованный контент. Полностью динамические маршруты пропускают этот кэш, но маршруты со статическими сегментами или ревалидацией всё равно могут генерировать статические оболочки.
Client-side Router Cache
Браузер хранит RSC payload в памяти во время навигации. Layouts сохраняются при переходах между маршрутами. Посещенные маршруты кэшируются в памяти на время сессии и переиспользуются при навигации до момента инвалидации.
Эти слои работают каскадно: инвалидация Data Cache влияет на последующие запросы, а Full Route Cache или Router Cache обновляются при следующем рендере, требующем свежих данных.
Создание переиспользуемых кэшируемых серверных компонентов
Ментальная модель, которая предотвращает ошибки с устаревшими данными: компоненты должны управлять своей политикой кэширования.
// lib/data.ts
import { cache } from 'react'
export const getProduct = cache(async (id: string) => {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 300, tags: ['products', `product-${id}`] }
})
return res.json()
})
Эту функцию можно вызывать из любого компонента — layout, page или вложенного дочернего компонента. cache() из React дедуплицирует вызовы в рамках одного прохода рендеринга. Опция next.revalidate контролирует время жизни в Data Cache. Теги позволяют точечно инвалидировать кэш.
Используйте эту функцию где угодно без prop drilling:
// app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: { id: string } }) {
const { id } = await params
const product = await getProduct(id)
return <ProductDetails product={product} />
}
Управление поведением кэша с помощью опций сегментов маршрута
Помимо опций fetch, конфигурации сегментов маршрута контролируют кэширование на уровне маршрута:
// Принудительный динамический рендеринг
export const dynamic = 'force-dynamic'
// Установка периода ревалидации для всех fetch-запросов
export const revalidate = 3600
Для инвалидации по требованию используйте revalidatePath или ревалидацию на основе тегов:
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function updateProduct(id: string) {
// await db.product.update(...)
revalidateTag(`product-${id}`)
}
Discover how at OpenReplay.com.
Next.js Partial Prerendering: экспериментальное направление
Next.js Partial Prerendering представляет собой значительный сдвиг. Вместо выбора между полностью статическими или полностью динамическими маршрутами, PPR предварительно отрисовывает статическую оболочку, одновременно потоково передавая динамический контент через границы Suspense.
import { Suspense } from 'react'
export default async function ProductPage({ params }: { params: { id: string } }) {
const { id } = await params
return (
<>
{/* Статическая оболочка - предварительно отрисована */}
<Header />
<ProductInfo id={id} />
{/* Динамическая область - передается потоком во время запроса */}
<Suspense fallback={<CartSkeleton />}>
<UserCart />
</Suspense>
</>
)
}
Статические части отдаются мгновенно. Динамические секции передаются потоком по мере их разрешения. Эта функция всё ещё экспериментальная — включите её через ppr: true в экспериментальных опциях вашего next.config.js — но она указывает на будущее кэширования в Next.js.
React 19 и кэширование на уровне компонентов
Улучшенное поведение Suspense в React 19 позволяет применять более детальные стратегии кэширования. Компоненты, обернутые в Suspense, могут независимо управлять жизненным циклом своих данных. Используя экспериментальные API use cache и cacheLife, вы можете кэшировать поддеревья компонентов, а не целые страницы:
import { cacheLife } from 'next/cache'
async function BlogPosts() {
'use cache'
cacheLife('hours')
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return <PostList posts={posts} />
}
Ревалидация на основе тегов позволяет командам безопасно делиться кэшированными данными между маршрутами. Компонент продукта, используемый как в /products/[id], так и в /checkout, может быть инвалидирован один раз, обновляясь везде.
Заключение
Создавайте серверные компоненты, которые инкапсулируют свою политику кэширования. Используйте cache() из React для дедупликации запросов, опции fetch для контроля Data Cache и теги для инвалидации между маршрутами. Partial Prerendering экспериментален, но его стоит понимать — это направление, в котором движется кэширование в Next.js.
Цель — не максимальное кэширование. Цель — предсказуемое кэширование, соответствующее реальным требованиям к свежести ваших данных.
Часто задаваемые вопросы
cache() из React дедуплицирует вызовы функций в рамках одного прохода рендеринга, предотвращая многократную загрузку одних и тех же данных во время одного запроса. Кэширование fetch в Next.js сохраняет результаты между несколькими запросами и развертываниями, используя Data Cache. Используйте оба вместе: cache() для дедупликации во время рендеринга и опции fetch для сохранения между запросами.
Используйте revalidateTag, когда нужно инвалидировать конкретные данные на нескольких маршрутах, например, информацию о продукте, отображаемую как на странице деталей, так и на странице оформления заказа. Используйте revalidatePath, когда хотите инвалидировать все кэшированные данные для конкретного URL-пути. Теги предлагают более точный контроль, в то время как пути проще для инвалидации одного маршрута.
Проверьте вывод сборки на наличие индикаторов маршрутов. Статические маршруты отображаются с иконкой круга, а динамические — с символом лямбды. Вы также можете добавить console.log в свои компоненты — если они логируются при каждом запросе, маршрут динамический. Использование cookies(), headers() или searchParams автоматически переводит маршруты в режим динамического рендеринга.
Partial Prerendering всё ещё экспериментален по состоянию на Next.js 15. Хотя вы можете включить его для тестирования, он пока не рекомендуется для продакшен-приложений. API и поведение могут измениться в будущих релизах. Следите за документацией Next.js и примечаниями к релизам для информации о стабильности перед внедрением в продакшен.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.