Пошук уроків, статей та іншого контенту
Додаватимете теги до кешованих запитів і пов’язуватимете їх із подальшою інвалідацією.
cacheTagcacheTag додає один або кілька тегів до результату, який кешується за допомогою директиви "use cache".
Тег не створює кеш самостійно. Він лише пов’язує кешований результат із назвою:
posts
post:42Пізніше цей тег можна використати для інвалідації кешу:
revalidateTag('posts', 'max')У результаті всі кешовані значення, позначені тегом posts, вважатимуться застарілими.
import { cacheTag } from 'next/cache';
export async function getData() {
'use cache';
cacheTag('data');
// Результат функції кешується
return fetch('https://example.com/api/data').then((response) =>
response.json()
);
}Виклик cacheTag має перебувати всередині кешованої функції або компонента, де використовується "use cache".
Одна функція може мати кілька тегів:
cacheTag('posts', 'post:42');Це дає змогу інвалідувати кеш або для всіх постів, або лише для одного конкретного поста.
Для використання "use cache" і cacheTag у сучасному Next.js потрібно ввімкнути Cache Components:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;Після зміни конфігурації перезапустіть сервер розробки.
Створімо функцію, яка отримує пост і додає до результату два теги:
posts — спільний тег для всіх постів;
post:${id} — унікальний тег конкретного поста.
// app/lib/posts.ts
import { cacheTag } from 'next/cache';
export type Post = {
userId: number;
id: number;
title: string;
body: string;
};
export async function getPost(id: number): Promise<Post> {
'use cache';
cacheTag('posts', `post:${id}`);
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
if (!response.ok) {
throw new Error('Не вдалося завантажити пост');
}
return response.json();
}Тепер результат getPost(1) кешується з такими тегами:
posts
post:1А результат getPost(2) — з тегами:
posts
post:2Тому можна окремо інвалідувати пост 1 або всі пости разом.
// app/posts/[id]/page.tsx
import { getPost } from '@/app/lib/posts';
type PageProps = {
params: Promise<{ id: string }>;
};
export default async function PostPage({ params }: PageProps) {
const { id } = await params;
const post = await getPost(Number(id));
return (
<main>
<h1>{post.title}</h1>
<p>{post.body}</p>
</main>
);
}Під час першого запиту Next.js виконає fetch і збереже результат у кеші. Наступні запити з тим самим аргументом id зможуть використати кешований результат.
revalidateTagДля інвалідації тегів використовується revalidateTag із пакета next/cache.
Наприклад, створімо Route Handler, який інвалідує кеш одного поста:
// app/api/posts/[id]/revalidate/route.ts
import { revalidateTag } from 'next/cache';
type RouteContext = {
params: Promise<{ id: string }>;
};
export async function POST(
_request: Request,
{ params }: RouteContext
) {
const { id } = await params;
// Позначаємо кеш конкретного поста як застарілий
revalidateTag(`post:${id}`, 'max');
return Response.json({
revalidated: true,
tag: `post:${id}`,
});
}Після POST-запиту до цього маршруту кеш поста буде інвалідовано:
curl -X POST http://localhost:3000/api/posts/1/revalidateТег post:1 пов’язаний із результатом getPost(1), тому наступне отримання цього поста більше не використовуватиме актуальний кешований результат без перевірки.
Оскільки кожен пост також має спільний тег posts, можна інвалідувати всі пости одним викликом:
// app/api/posts/revalidate/route.ts
import { revalidateTag } from 'next/cache';
export async function POST() {
// Позначаємо застарілими всі кешовані результати з тегом posts
revalidateTag('posts', 'max');
return Response.json({
revalidated: true,
tag: 'posts',
});
}Цей підхід корисний після операцій, які можуть змінити багато записів, наприклад:
імпорту списку постів;
масового оновлення;
видалення категорії;
зміни даних, які відображаються у всіх елементах списку.
'max'У сучасному API revalidateTag бажано викликати з профілем 'max':
revalidateTag('posts', 'max');Такий режим використовує модель stale-while-revalidate:
кешовані дані позначаються застарілими;
запит може тимчасово отримати попереднє значення;
Next.js оновлює дані у фоновому режимі;
наступні запити отримують оновлений результат.
revalidateTag не призначений для миттєвого блокування поточного запиту до завершення оновлення даних. Його основна мета — повідомити Next.js, що певна група кешованих результатів більше не є актуальною.
Інвалідацію потрібно виконувати після успішної зміни даних, а не до неї.
Наприклад, порядок дій у серверній операції має бути таким:
// app/actions/posts.ts
'use server';
import { updateTag } from 'next/cache';
export async function updatePost(id: number, title: string) {
// Тут у реальному застосунку відбувається оновлення запису в базі даних
await savePostToDatabase(id, title);
// Після успішного оновлення видаляємо пов’язаний кеш
updateTag(`post:${id}`);
}
async function savePostToDatabase(id: number, title: string) {
console.log(`Оновлено пост ${id}: ${title}`);
}updateTag призначений для Server Actions і негайно протерміновує кешований результат із вказаним тегом. Це відрізняється від revalidateTag(..., 'max'), який використовує модель stale-while-revalidate.
Вибір залежить від сценарію:
revalidateTag(tag, 'max) — для інвалідації через Route Handler або фонового оновлення;
updateTag(tag) — для Server Action, коли користувач очікує побачити власну щойно збережену зміну.
Практична схема тегів для ресурсів має такий вигляд:
cacheTag('posts', `post:${id}`);Вона дозволяє виконувати різні операції:
// Інвалідувати лише пост із id 42
revalidateTag('post:42', 'max');
// Інвалідувати кеш усіх постів
revalidateTag('posts', 'max');Завдяки цьому не потрібно щоразу скидати весь кеш застосунку, якщо змінився лише один ресурс.
cacheTag не замінює "use cache"Цей код не створює кешований результат:
import { cacheTag } from 'next/cache';
export async function getData() {
cacheTag('data');
return { value: 1 };
}Для кешування функція має перебувати в кешованому контексті:
import { cacheTag } from 'next/cache';
export async function getData() {
'use cache';
cacheTag('data');
return { value: 1 };
}revalidateTag працює лише для кешованих результатів, до яких було додано відповідний тег через cacheTag.
Якщо функція не використовує цей тег, виклик:
revalidateTag('unknown-tag', 'max');не вплине на її результат.
Тег не визначає, яке значення поверне функція, і не замінює аргументи функції. Аргументи впливають на окремі кешовані записи, а тег об’єднує ці записи для подальшої інвалідації.
Якщо спочатку викликати revalidateTag, а потім оновити базу даних, інший запит може встигнути отримати старі дані й повторно покласти їх у кеш.
Правильний порядок:
змінити дані;
переконатися, що операція завершилася успішно;
інвалідувати відповідний тег.
cacheTag поза кешованою функцією// Неправильно
cacheTag('posts');
export async function getPosts() {
'use cache';
return fetch('/api/posts');
}Виклик має бути всередині області "use cache":
export async function getPosts() {
'use cache';
cacheTag('posts');
return fetch('/api/posts');
}Якщо при кешуванні використано:
cacheTag(`post:${id}`);то інвалідувати потрібно точно такий самий тег:
revalidateTag(`post:${id}`, 'max');Виклик revalidateTag('posts', 'max') не інвалідує цей запис, якщо тег posts до нього не додавався.
Якщо список постів кешується з тегом posts, але окремі пости отримують лише теги post:${id}, то інвалідація posts не оновить кеш окремих постів.
Для підтримки обох сценаріїв додавайте обидва теги:
cacheTag('posts', `post:${id}`);cacheTag додає мітки до результатів, кешованих через "use cache".
Один результат може мати кілька тегів.
Спільні теги зручно використовувати для інвалідації колекцій.
Унікальні теги дають змогу інвалідувати один запис.
revalidateTag(tag, 'max') позначає пов’язані кешовані результати застарілими.
updateTag(tag) підходить для негайної інвалідації в Server Actions.
Інвалідацію слід виконувати після успішної зміни даних.