Директива «use cache» в Next.js 16: полное руководство по кэшированию

Разбираем директиву use cache в Next.js 16: три варианта кэширования, управление временем жизни через cacheLife, инвалидация с cacheTag и updateTag, миграция с unstable_cache и интеграция с Partial Prerendering.

use cache в Next.js 16: полный гайд 2026

Почему кэширование в Next.js 16 изменилось кардинально

Если вы работали с предыдущими версиями Next.js, то наверняка сталкивались с одной неприятной особенностью: кэширование было неявным. Фреймворк автоматически кэшировал fetch-запросы и статические страницы, а разработчикам приходилось отключать это через cache: 'no-store' или dynamic = 'force-dynamic'. Честно говоря, это порождало кучу путаницы — данные кэшировались когда не нужно, а ревалидация работала как-то... непредсказуемо.

Next.js 16 перевернул эту модель с ног на голову. Теперь весь код динамический по умолчанию, а кэширование стало явным и декларативным через новую директиву "use cache". Каждый маршрут, компонент или функция выполняются в реальном времени, пока вы явно не скажете: «Эй, этот результат можно кэшировать».

Итак, давайте разберём всё по порядку — от базового применения use cache до продвинутых стратегий ревалидации с cacheTag и updateTag.

Что такое директива «use cache»

Директива "use cache" — это новый примитив языкового уровня в Next.js 16. Работает она по тому же принципу, что и "use client" или "use server": добавляете строку в начало функции или файла, и компилятор понимает — результат выполнения можно кэшировать.

И вот что действительно круто: в отличие от устаревшего unstable_cache, который требовал ручного управления ключами и обёртывания функций, use cache интегрирован прямо в компилятор. Он анализирует аргументы функции, замыкания и даже props компонентов, чтобы автоматически генерировать ключ кэша. Никаких магических строк.

Где можно применять «use cache»

Директива работает на трёх уровнях:

  • Уровень функции — кэширование результата асинхронной функции (например, запроса к базе данных)
  • Уровень компонента — кэширование отрендеренного вывода серверного компонента
  • Уровень страницы/лейаута — кэширование всего маршрута целиком

Включение Cache Components в проекте

Прежде чем начать использовать use cache, нужно включить Cache Components в конфигурации:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

После этого становятся доступны все три варианта директивы и связанные API: cacheLife, cacheTag, updateTag.

Минимальные требования: Node.js 20.9.0 и TypeScript 5.1.0. Если используете более старые версии — время обновиться.

Три варианта директивы «use cache»

Next.js 16 предоставляет три варианта кэширования. Каждый — для своего сценария.

1. «use cache» — кэширование в памяти (по умолчанию)

Стандартная директива хранит данные в оперативной памяти сервера с использованием LRU-алгоритма. Для большинства задач — это именно то, что нужно.

async function getProducts() {
  'use cache'
  const products = await db.product.findMany()
  return products
}

Есть нюанс: кэш теряется при перезапуске сервера и может вытесняться при нехватке памяти. И данные не разделяются между экземплярами — так что для одиночного инстанса это идеально, а для кластера стоит посмотреть дальше.

2. «use cache: remote» — удалённый кэш

Если у вас несколько экземпляров сервера в продакшене (а в 2026 году это скорее норма), понадобится общее хранилище — Redis, KV или что-то подобное:

async function getGlobalConfig() {
  'use cache: remote'
  const config = await fetch('https://api.example.com/config')
  return config.json()
}

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

3. «use cache: private» — клиентский кэш

Это экспериментальная штука. Она позволяет обращаться к runtime API (cookies(), headers(), searchParams) внутри кэшированного контекста:

async function getUserPreferences() {
  'use cache: private'
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')?.value ?? 'light'
  return { theme }
}

Важный момент: результаты use cache: private кэшируются только в памяти браузера. На сервере ничего не сохраняется. Перезагрузил страницу — кэша нет.

Управление временем жизни кэша с cacheLife

Функция cacheLife из next/cache задаёт политику кэширования. У неё три параметра, и каждый отвечает за свой аспект:

  • stale — как долго клиент использует кэш без проверки сервера
  • revalidate — после истечения этого времени следующий запрос запустит фоновое обновление
  • expire — после этого времени без запросов кэш удаляется полностью

Если это напоминает вам стратегию stale-while-revalidate из HTTP-кэширования — вы абсолютно правы, логика та же.

Встроенные профили

Для типичных сценариев есть готовые профили, чтобы не задавать числа вручную каждый раз:

import { cacheLife } from 'next/cache'

async function getStaticContent() {
  'use cache'
  cacheLife('days')     // Для редко меняющегося контента
  return await fetchContent()
}

async function getFrequentData() {
  'use cache'
  cacheLife('hours')    // Для данных средней частоты обновлений
  return await fetchData()
}

async function getLiveData() {
  'use cache'
  cacheLife('minutes')  // Для часто меняющихся данных
  return await fetchLiveData()
}

Пользовательские профили

Когда встроенных профилей недостаточно, можно определить свои в конфигурации. На практике это встречается довольно часто:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
  cacheLife: {
    catalog: {
      stale: 3600,       // 1 час — клиент использует кэш
      revalidate: 900,   // 15 минут — фоновое обновление
      expire: 86400,     // 24 часа — полное удаление
    },
    userSession: {
      stale: 300,        // 5 минут
      revalidate: 60,    // 1 минута
      expire: 3600,      // 1 час
    },
  },
}

export default nextConfig

А дальше используете профиль по имени — просто и читаемо:

async function getProductCatalog() {
  'use cache'
  cacheLife('catalog')
  return await db.product.findMany({ where: { isActive: true } })
}

Условное кэширование

Вот паттерн, который я считаю особенно элегантным — разная длительность кэша в зависимости от результата:

import { cacheLife, cacheTag } from 'next/cache'

async function getArticle(slug: string) {
  'use cache'
  cacheTag(`article-${slug}`)

  const article = await db.article.findUnique({ where: { slug } })

  if (!article) {
    cacheLife('minutes')  // Ненайденные статьи — кэшируем ненадолго
    return null
  }

  cacheLife('days')       // Опубликованный контент — кэшируем надолго
  return article
}

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

Инвалидация кэша: cacheTag, revalidateTag и updateTag

Временное кэширование через cacheLife — это хорошо, но часто нужно сбрасывать кэш по событию. Скажем, редактор обновил статью — и пользователи должны видеть свежую версию.

Пометка кэша тегами

Функция cacheTag привязывает кэш-запись к одному или нескольким тегам:

import { cacheTag, cacheLife } from 'next/cache'

async function getUserProfile(userId: string) {
  'use cache'
  cacheLife('hours')
  cacheTag('users', `user-${userId}`)
  return await db.user.findUnique({ where: { id: userId } })
}

revalidateTag — фоновое обновление

revalidateTag помечает все кэш-записи с указанным тегом как устаревшие. При следующем запросе сервер отдаёт старые данные (stale-while-revalidate), а в фоне обновляет кэш:

'use server'
import { revalidateTag } from 'next/cache'

export async function updateUserRole(userId: string, role: string) {
  await db.user.update({ where: { id: userId }, data: { role } })
  revalidateTag(`user-${userId}`, 'max')
}

Обратите внимание: начиная с Next.js 16.2+, одноаргументная форма revalidateTag(tag) устарела. Теперь нужно использовать двухаргументную: revalidateTag(tag, 'max').

updateTag — мгновенная инвалидация

Бывают случаи, когда пользователь должен увидеть изменения сразу — паттерн «read your own writes». Для этого есть updateTag:

'use server'
import { updateTag } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  const post = await db.post.create({
    data: { title, content },
  })

  updateTag('posts')            // Немедленно сбрасывает кэш
  redirect(`/posts/${post.id}`)  // Пользователь видит свежие данные
}

Ключевое отличие от revalidateTag: updateTag немедленно удаляет кэш, без stale-while-revalidate. Но есть ограничение — updateTag доступен только в Server Actions.

Миграция с unstable_cache на «use cache»

Если ваш проект ещё использует unstable_cache, пора переезжать. Но учтите — это не простая замена одного вызова на другой. Это переход на новую архитектуру.

До: unstable_cache (Next.js 15)

import { unstable_cache } from 'next/cache'

export const getCachedUser = (userId: string) => {
  return unstable_cache(
    async () => {
      return db.user.findUnique({ where: { id: userId } })
    },
    [`user-${userId}`],                    // Ручные ключи кэша
    { tags: [`user-${userId}`], revalidate: 3600 }
  )()
}

После: «use cache» (Next.js 16)

import { cacheLife, cacheTag } from 'next/cache'

async function getUser(userId: string) {
  'use cache'
  cacheLife('hours')
  cacheTag(`user-${userId}`)
  return db.user.findUnique({ where: { id: userId } })
}

Разница видна невооружённым глазом:

  • Нет обёртки — функция выглядит как обычный async-вызов
  • Ключи кэша генерируются автоматически на основе аргументов
  • Теги и время жизни задаются декларативно, прямо внутри функции
  • use cache может кэшировать не только JSON, но и компоненты, и целые маршруты

Пошаговый план миграции

  1. Включите cacheComponents: true в next.config.ts
  2. Найдите все вызовы unstable_cache в проекте (обычный grep справится)
  3. Для каждого вызова: извлеките функцию из обёртки, добавьте "use cache" в начало
  4. Замените числовой revalidate на профиль cacheLife
  5. Замените массив tags на вызовы cacheTag
  6. Обновите все revalidateTag на двухаргументную форму
  7. Проверьте, что cookies() и headers() не вызываются внутри use cache — вынесите их наружу

По моему опыту, миграция среднего проекта занимает пару часов. Основная сложность — не в синтаксисе, а в пересмотре стратегии кэширования.

Интеграция с Partial Prerendering (PPR)

Директива use cache тесно связана с Partial Prerendering — и это, пожалуй, самая интересная часть. PPR позволяет объединить статический и динамический контент на одной странице.

Как это работает

При сборке Next.js создаёт статическую оболочку из кэшированных частей страницы. Динамические части оборачиваются в <Suspense> и стримятся клиенту при запросе. Вот как это выглядит на практике:

import { Suspense } from 'react'
import { cacheLife, cacheTag } from 'next/cache'

// Эта часть попадёт в статическую оболочку
async function ProductCatalog() {
  'use cache'
  cacheLife('hours')
  cacheTag('catalog')

  const products = await db.product.findMany({
    where: { isActive: true },
    orderBy: { createdAt: 'desc' },
  })

  return (
    

Каталог товаров

    {products.map(p => (
  • {p.name} — {p.price} ₽
  • ))}
) } // Динамическая часть — стримится при запросе async function UserCart() { const cookieStore = await cookies() const cartId = cookieStore.get('cartId')?.value const cart = await db.cart.findUnique({ where: { id: cartId } }) return } // Страница объединяет оба подхода export default function ShopPage() { return (
}>
) }

Результат впечатляет: пользователь мгновенно видит каталог из кэша, а корзина подгружается параллельно. TTFB снижается на 60–80% по сравнению с полностью динамическими страницами. На реальных проектах это ощутимая разница.

Типичные ошибки и подводные камни

При работе с use cache легко наступить на грабли. Вот самые частые — чтобы вы не наступали.

1. Вызов runtime API внутри «use cache»

Нельзя вызывать cookies(), headers() или обращаться к searchParams внутри функции с "use cache". Эти API возвращают данные, уникальные для каждого запроса, и кэшировать их бессмысленно (и опасно).

// ❌ Неправильно
async function getDashboard() {
  'use cache'
  const cookieStore = await cookies()  // Ошибка!
  const userId = cookieStore.get('userId')?.value
  return await fetchDashboard(userId)
}

// ✅ Правильно
async function getDashboardData(userId: string) {
  'use cache'
  cacheTag(`dashboard-${userId}`)
  return await fetchDashboard(userId)
}

// В компоненте — извлекаем userId до кэшированного вызова
export default async function DashboardPage() {
  const cookieStore = await cookies()
  const userId = cookieStore.get('userId')?.value
  const data = await getDashboardData(userId!)
  return 
}

2. Слишком широкое кэширование

Не размещайте use cache на уровне лейаута, если дочерние компоненты зависят от динамических данных. Кэшируйте ближе к источнику данных:

// ❌ Слишком широко — кэширует весь лейаут
export default async function Layout({ children }) {
  'use cache'
  return 
{children}
} // ✅ Точечно — кэшируем только данные навигации async function getNavItems() { 'use cache' cacheLife('days') return await db.navItem.findMany({ orderBy: { sortOrder: 'asc' } }) }

3. Кэш с очень коротким временем жизни

Кэш с revalidate менее 5 минут или нулевым expire автоматически исключается из пререндеринга и становится динамической «дырой». Это by design, но может застать врасплох, если вы рассчитывали на статическую оболочку.

4. Несовместимость с Edge Runtime

Директива use cache работает только в среде Node.js. Для Edge Runtime используйте fetch() с опцией next.revalidate. Это ограничение архитектурное, и вряд ли оно изменится в ближайших релизах.

Лучшие практики кэширования в Next.js 16

Несколько рекомендаций, основанных на реальном опыте работы с новой системой:

  • Кэшируйте данные, а не UI — размещайте use cache на функциях доступа к данным, а не на компонентах (если нет веской причины)
  • Используйте теги для связанных данных — группируйте кэш-записи тегами ('products', 'users'), чтобы инвалидировать их одним вызовом
  • Предпочитайте cacheTag + revalidateTag вместо cacheLife для контента из CMS — не ждите таймера, сбрасывайте кэш при публикации
  • Используйте updateTag в Server Actions для операций, где пользователь ждёт мгновенного результата
  • Определяйте пользовательские профили в next.config.ts для единообразия во всём проекте
  • Тестируйте в dev-режиме — Next.js 16 показывает предупреждения, если динамические данные используются вне <Suspense>

Часто задаваемые вопросы

Чем «use cache» отличается от unstable_cache?

unstable_cache — это библиотечная функция с ручным управлением ключами. "use cache" — директива компилятора: добавляете строку, а Next.js сам генерирует ключи на основе аргументов и замыканий. Плюс use cache умеет кэшировать компоненты и маршруты, а не только JSON.

Можно ли использовать «use cache» с динамическими маршрутами?

Да, без проблем. Аргументы функции (включая параметры маршрута) автоматически становятся частью ключа кэша. Каждая комбинация параметров — отдельная кэш-запись. Так что getProduct(productId) создаст свой кэш для каждого productId.

Как работает кэширование при деплое на Vercel?

На Vercel "use cache" использует встроенное in-memory LRU-хранилище по умолчанию. Для "use cache: remote" Vercel предоставляет интегрированный KV-хранилище через конфигурацию cacheHandlers. Это позволяет разделять кэш между серверлесс-функциями и edge-нодами.

Что произойдёт, если кэшированная функция выбросит ошибку?

Ошибки не кэшируются — и это правильное решение. Если функция внутри use cache бросает исключение, оно пробрасывается вызывающему коду, а в кэш ничего не записывается. Следующий запрос повторит вызов. Для предсказуемых ошибок лучше возвращать объект результата вместо throw.

Поддерживает ли «use cache» работу с Prisma и Drizzle ORM?

Да, use cache совместим с любым ORM. Кэшируется сериализуемый результат функции, поэтому неважно, используете вы Prisma, Drizzle, Kysely или прямые SQL-запросы. Единственное условие — возвращаемые данные должны быть сериализуемы (без функций, классов или циклических ссылок).

Об авторе Priya Raghavan

Priya spent four years on Vercel's developer experience team, where she wrote a chunk of the Turbopack incremental cache layer and rewrote the official App Router migration codemods used by tens of thousands of teams. Before that, she shipped the checkout funnel rewrite at Shopify on Next.js 12, cutting LCP from 3.4s to 1.1s on mid-tier Android. These days she runs an independent consultancy helping Series B companies untangle Pages Router monoliths into RSC-first App Router architectures. She has opinions about Server Actions, mostly negative ones, and is slowly coming around on Partial Prerendering after benchmarking it against six production workloads. She writes about cache semantics, streaming SSR, and the parts of the React docs that quietly contradict the Next.js docs. Eight years in frontend, three of them spent yelling at hydration mismatches.