Директива «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-запросы. Единственное условие — возвращаемые данные должны быть сериализуемы (без функций, классов или циклических ссылок).

Об авторе Editorial Team

Our team of expert writers and editors.