Next.jsMiddleTechnical
Что такое unstable_cache и revalidateTag?
unstable_cache кэширует результат произвольной async-функции на сервере с тегами и TTL. revalidateTag инвалидирует все записи с указанным тегом по требованию.
unstable_cache и revalidateTag в Next.js
unstable_cache — функция из пакета next/cache, которая позволяет кэшировать результат любой асинхронной операции на сервере (запрос к БД, вызов стороннего SDK, файловая система) — не только fetch. Несмотря на префикс unstable_, функция стабильна в продакшне и широко используется; в Next.js 15 появился стабильный аналог cacheTag/cacheLife, но unstable_cache остаётся рабочим API.
Сигнатура
import { unstable_cache } from "next/cache";
const cachedFn = unstable_cache(
async (...args) => { /* вычисление */ },
keyParts, // string[] — часть ключа кэша
options // { tags?: string[], revalidate?: number | false }
);
Пример: кэширование запроса к БД
// lib/data/products.ts
import { unstable_cache } from "next/cache";
import { db } from "@/lib/db";
export const getProducts = unstable_cache(
async (categoryId: string) => {
const result = await db.query(
"SELECT id, name, price FROM products WHERE category_id = $1",
[categoryId]
);
return result.rows;
},
["products-by-category"], // базовый ключ
{
tags: ["products"], // теги для инвалидации
revalidate: 3600, // TTL в секундах (1 час)
}
);
// app/categories/[id]/page.tsx
import { getProducts } from "@/lib/data/products";
export default async function CategoryPage({
params,
}: {
params: { id: string };
}) {
// Первый вызов — БД, последующие — кэш
const products = await getProducts(params.id);
return (
<ul>
{products.map((p) => (
<li key={p.id}>{p.name} — {p.price} ₽</li>
))}
</ul>
);
}
revalidateTag: инвалидация по тегу
revalidateTag сбрасывает все кэш-записи, помеченные указанным тегом. Вызывается из Server Actions, Route Handlers или серверного кода.
// app/actions/products.ts
"use server";
import { revalidateTag } from "next/cache";
import { db } from "@/lib/db";
export async function updateProductPrice(
productId: string,
newPrice: number
) {
await db.query(
"UPDATE products SET price = $1 WHERE id = $2",
[newPrice, productId]
);
// Инвалидируем все кэши с тегом "products"
revalidateTag("products");
}
Route Handler для внешних вебхуков
// app/api/webhooks/product-updated/route.ts
import { revalidateTag } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const secret = req.headers.get("x-webhook-secret");
if (secret !== process.env.WEBHOOK_SECRET) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
revalidateTag("products");
return NextResponse.json({ revalidated: true });
}
Гранулярные теги для точечной инвалидации
// lib/data/products.ts
export const getProductById = unstable_cache(
async (id: string) => {
const result = await db.query(
"SELECT * FROM products WHERE id = $1",
[id]
);
return result.rows[0];
},
["product"],
{
// Тег вида "product-{id}" позволяет инвалидировать один товар
tags: (id: string) => [`product-${id}`, "products"],
revalidate: 600,
} as Parameters<typeof unstable_cache>[2]
);
Подводные камни
- Ключ кэша не включает аргументы автоматически: массив
keyParts— статический; аргументы функции учитываются Next.js отдельно, но если два разных вызова с разными аргументами дадут одинаковыйkeyParts— возможны коллизии. Добавляйте описательные строки вkeyParts. - revalidateTag не работает в Edge Runtime по умолчанию: убедитесь, что Route Handler или Action работают в Node.js runtime при использовании файловой системы кэша.
- Кэш привязан к деплою: при перезапуске сервера (новый деплой) in-memory кэш сбрасывается; для персистентного кэша между деплоями используйте Redis через
@vercel/kvили собственный адаптер. - Несериализуемые возвращаемые значения ломают кэш: функция должна возвращать JSON-совместимые данные; классовые экземпляры, Map, Set вызовут ошибку сериализации.
- revalidate: false означает постоянный кэш, который очищается только через
revalidateTagили перезапуск — убедитесь, что механизм инвалидации реализован. - Отсутствие тегов делает инвалидацию невозможной: без тегов единственный способ очистить кэш — истечение TTL или новый деплой.
- unstable_cache не работает внутри Client Components: это серверная функция; вызов в клиентском контексте приведёт к ошибке во время выполнения.
- Параллельные мутации могут опередить инвалидацию: при высокой нагрузке между записью в БД и вызовом
revalidateTagвозможна гонка; рассмотрите транзакционную инвалидацию или короткий TTL.
Common mistakes
- Передавать сложные объекты как аргументы
- Кешировать персональные данные без userId в ключе
- Использовать
revalidate: falseбез событийной инвалидации - Игнорировать различия self-hosted при кластеризации
What the interviewer is testing
- Знает, что
unstable_cacheдля не-fetch функций - Использует иерархичные теги
- Понимает риск ключа из больших объектов
- Учитывает self-hosted ограничения