В предыдущих версиях Next.js модель кэширования строилась на неявных эвристиках вокруг глобального fetch: методы запросов автоматически мемоизировались, а маршруты пытались статически скомпилироваться при сборке, если не обнаруживали явных вызовов динамических API. Это приводило к скрытым побочным эффектам: случайное добавление заголовка ломало статику всего маршрута, а мутации данных требовали сложного разбора многослойного кэша (Data Cache, Full Route Cache, Router Cache).
В Next.js 16 представлена парадигма Dynamic I/O и специализированная подсистема Cache Components.
Фундаментальный принцип новой архитектуры: все операции ввода-вывода (I/O) динамические по умолчанию. Если компоненту или функции требуется кэширование, разработчик явно объявляет это с помощью компиляторной директивы "use cache".
ЭВОЛЮЦИЯ ПАРАДИГМЫ КЭШИРОВАНИЯ NEXT.JS:
┌────────────────────────────────────────────────────────────────────────┐
│ Next.js 13-14 (Heuristic Caching): │
│ [fetch() по умолчанию кэшируется] ──► Неявная статика ──► Побочные баги │
└────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────┐
│ Next.js 16 (Dynamic I/O & Cache Components): │
│ [Любой I/O по умолчанию динамический] ──► Явная директива "use cache" │
│ ──► Компиляторная генерация ключей и изоляция RSC Flight Payload │
└────────────────────────────────────────────────────────────────────────┘В отличие от устаревшей функции unstable_cache, директива "use cache" является нативной инструкцией для компилятора Next.js (Turbopack / SWC). Компилятор переписывает AST функции или компонента, автоматически извлекая аргументы, переменные из замыкания и детерминированно связывая их с рантаймом кэширования.
Директива "use cache" формирует строгую изолированную границу вокруг асинхронного блока кода. Она может применяться на трех уровнях абстракции:
async function. Кэшируется результат возвращаемого значения (JSON-сериализуемые структуры данных: объекты, массивы, примитивы).При использовании unstable_cache разработчики вручную передавали массив строковых идентификаторов (keyParts), что постоянно приводило к коллизиям ключей или утечкам устаревших данных при изменении сигнатуры функции.
В директиве "use cache" компилятор генерирует композитный ключ кэша автоматически на основе трех составляющих:
АРХИТЕКТУРА СОСТАВНОГО КЛЮЧА КЭША:
┌────────────────────────────────────────────────────────────────────────┐
│ COMPOSITE CACHE KEY = HASH( │
│ Build ID + Module Specifier + Function AST Fingerprint + │
│ Deterministic_JSON(Captured Lexical Scope Variables) + │
│ Deterministic_JSON(Runtime Arguments) │
│ ) │
└────────────────────────────────────────────────────────────────────────┘В распределенных инфраструктурах Next.js 16 поддерживает варианты спецификатора директивы:
"use cache" — стандартное разделяемое серверное кэширование (Shared Server Cache). Кэш разделяется между всеми пользователями приложения."use cache: remote" — явное указание сохранять результат во внешнем распределенном хранилище (L2/L3: Redis, Memcached, S3), минуя локальную память пода."use cache: private" — контекстно-зависимый кэш (User/Session Scoped Cache). Привязывается к идентификатору сессии входящего запроса (Cookie/Authorization), исключая утечку персональных данных другим пользователям.Turbopack анализирует AST функции, фиксирует внешние переменные замыкания и внедряет вызов рантайма cacheHandler.
Рантайм вычисляет SHA-256 от сигнатуры функции, захваченных констант и переданных при вызове аргументов.
Запрос к локальному L1 (память процесса) или L2 (Redis). При Cache Hit сериализованный Flight Payload или JSON отдается мгновенно (менее 5 мс).
Код функции исполняется (запрос к БД, внешний API). Результат сериализуется и сохраняется в кэш с назначенными тегами и TTL.
Функция с директивой 'use cache' использует внутри себя переменную taxRate, объявленную во внешней области видимости модуля. Что произойдет, если значение taxRate изменится в рантайме или при сборке?
Сервис интернет-магазина запрашивает базовые цены из реляционной базы данных (PostgreSQL) и конвертирует их по матрице курсов валют. Директива на уровне функции гарантирует, что тяжелая математика и SQL-запрос выполняются только один раз для уникальной комбинации category и targetCurrency.
Серверный компонент документации запрашивает сырой Markdown из Git-репозитория, выполняет подсветку синтаксиса через Shiki и компилирует дерево компонентов. Директива на уровне компонента сохраняет в кэш не сырые данные, а готовый скомпилированный RSC Flight Payload. Браузер получает мгновенный стрим без повторного парсинга AST на стороне Node.js.
SDK взаимодействия с внутренним биллингом или ERP-системой. Размещение директивы в заголовке файла превращает весь модуль в кэширующий шлюз: все методы SDK автоматически защищают внутреннюю сеть компании от паразитных дублирующих запросов (Request Coalescing).
use cache: private)#Страница внутренней HR-системы отображает статистику отпусков и KPI сотрудника. Использование приватного модификатора позволяет закэшировать тяжелый агрегационный запрос на уровне изолированной пользовательской сессии без риска раскрытия приватных данных коллегам.
Для включения поддержки Cache Components в Next.js 16 в конфигурационном файле объявляется экспериментальный флаг dynamicIO:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
experimental: {
// Включение Dynamic I/O и компиляторных директив 'use cache'
dynamicIO: true,
},
};
export default nextConfig;import { db } from '@/lib/db';
export interface ProductCatalogFilter {
categoryId: string;
currency: 'USD' | 'EUR' | 'GBP';
inStockOnly: boolean;
}
// Функция с гранулярным кэшированием вычислений и запросов
export async function getCachedCatalog(filter: ProductCatalogFilter) {
'use cache';
// Компилятор автоматически генерирует ключ из filter.categoryId,
// filter.currency и filter.inStockOnly
const products = await db.product.findMany({
where: {
categoryId: filter.categoryId,
...(filter.inStockOnly ? { inventory: { gt: 0 } } : {}),
},
select: {
id: true,
title: true,
basePriceUSD: true,
},
});
const exchangeRate = await db.exchangeRates.findUnique({
where: { targetCurrency: filter.currency },
});
const rate = exchangeRate?.rate ?? 1.0;
return products.map((item) => ({
id: item.id,
title: item.title,
price: Math.round(item.basePriceUSD * rate * 100) / 100,
currency: filter.currency,
}));
}В отличие от традиционных функций, кэширование серверного компонента сохраняет сгенерированный виртуальный DOM в формате React Flight Payload. Внутренние серверные зависимости не вызываются повторно.
import { getCachedCatalog } from '@/lib/catalog';
import { ExpensiveBadge } from '@/components/ExpensiveBadge';
interface ShowcaseProps {
categoryId: string;
featuredOnly?: boolean;
}
// Кэшируется скомпилированное поддерево Flight Payload
export async function ProductShowcase({ categoryId, featuredOnly = false }: ShowcaseProps) {
'use cache';
const items = await getCachedCatalog({
categoryId,
currency: 'USD',
inStockOnly: true,
});
const displayItems = featuredOnly ? items.slice(0, 3) : items;
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 p-4">
{displayItems.map((product) => (
<article key={product.id} className="border border-border p-4 rounded-xl bg-bg-surface">
<h3 className="font-semibold text-lg text-text-primary">{product.title}</h3>
<p className="text-accent-cyan font-mono text-xl mt-2">
${product.price} {product.currency}
</p>
{/* Дочерние серверные компоненты также сериализуются в кэш */}
<ExpensiveBadge productId={product.id} />
</article>
))}
</div>
);
}Компилятор парсит AST, детектирует директиву 'use cache', выделяет захваченные константы области видимости и генерирует уникальный сигнатурный хэш.
Любые обращения к несериализуемым объектам вызывают ошибку компиляции.
При вызове функции рантайм сериализует аргументы и замыкание, формируя детерминированный ключ кэша для cacheHandler.
Используется бинарная детерминированная сериализация.
Опрос L1 In-Memory LRU кэша текущей реплики Node.js. При промахе — асинхронный запрос к внешнему L2 хранилищу (Redis).
Задержка при Cache Hit составляет менее 2 мс.
При Cache Miss результат исполнения функции или RSC-дерево сериализуется в Flight Payload и сохраняется в хранилище.
Автоматически регистрируются каскадные зависимости тегов.
Разработчик передал экземпляр класса Logger с методами и стрелочную функцию onClick в пропсы серверного компонента, помеченного директивой 'use cache'. Что произойдет при компиляции или вызове?
Если внутри функции или серверного компонента с директивой 'use cache' вызвать await cookies() или await headers(), компилятор Next.js выдаст фатальную ошибку сборки. Граница кэширования по определению изолирует код от специфичного контекста отдельного HTTP-запроса, делая его общим для всех клиентов.
Решение: Читайте динамические параметры (например, токен авторизации или валюту пользователя) снаружи границы кэша и передавайте полученное скалярное значение в качестве явного сериализуемого аргумента кэшируемой функции либо используйте специализированный модификатор 'use cache: private'.
// ❌ ОШИБКА: cookies() запрещен внутри shared кэша
export async function getBadUserData() {
'use cache';
const cookieStore = await cookies(); // Фатальная ошибка Dynamic I/O!
return fetchUserData(cookieStore.get('token')?.value);
}
// ✅ ПРАВИЛЬНО: передача скалярного аргумента через границу
export async function getGoodUserData(authToken: string) {
'use cache';
return fetchUserData(authToken);
}Если кэшируемая функция обращается к внешнему объекту, свойства которого динамически мутируют во времени (например, глобальный синглтон счетчика или кэш-мапа в памяти), композитный ключ кэша зафиксирует состояние объекта на момент первого создания AST-хэша, порождая трудноуловимые баги рассинхронизации.
Решение: Опирайтесь исключительно на иммутабельные константы или передавайте изменяемое состояние через аргументы.
Подсистема Cache Components и Dynamic I/O спроектирована для работы в Node.js runtime, так как требует потокового доступа к файловой системе, долговременным пулам соединений и продвинутым интерфейсам cacheHandler.
Решение: Не объявляйте export const runtime = 'edge' в маршрутах, использующих Cache Components. Используйте стандартный nodejs runtime.