1. Введение
Если вы когда-либо работали с кешированием, то знаете, что главная проблема — это "когда и что сбрасывать". Если кеш всегда свежий — мы теряем смысл кеша (и производительность). Если никогда не сбрасывать — пользователи будут видеть устаревшие данные. В Next.js 15 появилась очень удобная концепция — тэги кеша (cache tags).
Аналогия из жизни
Представьте себе библиотеку, где у каждой книги есть цветная наклейка — "тег". Когда книга обновляется (например, выходит новое издание), библиотекарь может сказать: "Всё, что с красной наклейкой — заменить!". Так и в Next.js: вы можете пометить кешированные данные одним или несколькими тегами, а потом по этому тегу сбросить только нужные части кеша.
Проблема без тегов
Допустим, у вас есть блог. На главной выводится список постов, а на каждой странице — отдельный пост. Пользователь добавил новый пост — надо бы обновить только список, а не все страницы. Или кто-то отредактировал пост — надо обновить только его страницу и, возможно, список. Если бы мы не могли "пометить" кешированные данные, пришлось бы сбрасывать всё или городить сложные схемы.
Как работают tags в Next.js 15
В Next.js 15 вы можете передать массив тегов в функцию fetch через опцию next: { tags: [...] }. Любые компоненты или страницы, которые используют такой fetch, будут закешированы с этим тегом. Позже, когда вы вызовете функцию revalidateTag('имя_тега'), Next.js сбросит кеш для всех данных, связанных с этим тегом, и при следующем запросе данные будут обновлены.
Важно!
- Tags работают только на сервере (Server Components, Route Handlers).
- Tags не работают в Client Components.
- Функция revalidateTag() должна вызываться на сервере (например, в Server Action или Route Handler).
2. Пример использования tags: кешируем список задач
Давайте продолжим развивать ваше учебное приложение — простой TODO-лист на Next.js 15. Допустим, у нас есть страница /tasks, которая показывает список задач, и мы хотим, чтобы этот список кешировался, но обновлялся при добавлении или удалении задачи.
Кешируемый fetch с тегом
// app/tasks/page.tsx — Server Component
async function getTasks() {
const res = await fetch('https://api.example.com/tasks', {
next: { tags: ['tasks'] }, // <- ВАЖНО: указываем тег!
});
return res.json();
}
export default async function TasksPage() {
const tasks = await getTasks();
return (
<div>
<h1>Список задач</h1>
<ul>
{tasks.map((task: any) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
</div>
);
}
Теперь Next.js будет кешировать результат запроса с тегом 'tasks'. Пока тег не сброшен, все пользователи будут видеть один и тот же список задач (очень быстро!).
Обновление данных и сброс кеша с помощью revalidateTag()
Допустим, у нас есть форма для добавления новой задачи. После успешного добавления мы хотим, чтобы список задач обновился для всех пользователей.
Server Action для добавления задачи:
// app/tasks/actions.ts
'use server';
import { revalidateTag } from 'next/cache';
export async function addTask(formData: FormData) {
const title = formData.get('title');
// Отправляем POST-запрос на API для добавления задачи
await fetch('https://api.example.com/tasks', {
method: 'POST',
body: JSON.stringify({ title }),
headers: { 'Content-Type': 'application/json' },
});
// Сбрасываем кеш для тега 'tasks'
revalidateTag('tasks');
}
Форма для добавления задачи:
// app/tasks/AddTaskForm.tsx
'use client';
import { useRef } from 'react';
import { addTask } from './actions';
export default function AddTaskForm() {
const formRef = useRef<HTMLFormElement>(null);
return (
<form
ref={formRef}
action={async (formData) => {
await addTask(formData);
formRef.current?.reset();
}}
>
<input name="title" placeholder="Новая задача" required />
<button type="submit">Добавить</button>
</form>
);
}
Итоговая страница:
// app/tasks/page.tsx
import AddTaskForm from './AddTaskForm';
export default async function TasksPage() {
const tasks = await getTasks();
return (
<div>
<h1>Список задач</h1>
<AddTaskForm />
<ul>
{tasks.map((task: any) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
</div>
);
}
Теперь, когда пользователь добавляет задачу, вызывается Server Action, которая:
- Добавляет задачу в базу.
- Вызывает revalidateTag('tasks').
- Next.js сбрасывает кеш для всех fetch-запросов с тегом 'tasks'.
- При следующем открытии страницы (или навигации) данные будут свежими!
3. Полезные нюансы
Как устроен revalidateTag() внутри
Функция revalidateTag() — это серверная функция из пакета next/cache. Она сообщает Next.js: "Все данные, кешированные с этим тегом, считаются устаревшими, надо их обновить при следующем запросе". Это очень удобно для инкрементальной регенерации страниц (ISR) и для динамических проектов.
Важно: Вызывать revalidateTag() можно только на сервере (в Server Actions, Route Handlers или Server Components с "use server").
Когда использовать tags и revalidateTag()
- Списки, которые часто меняются: Например, список товаров, постов, задач.
- Детальные страницы: Если вы редактируете отдельный объект (например, пост), можно использовать отдельный тег, например post-42, и сбрасывать кеш только для него.
- Группы данных: Один тег может быть у нескольких fetch-запросов — это удобно, если нужно сбросить кеш сразу для нескольких связанных компонентов.
Пример: индивидуальные теги для постов
// Получение одного поста с тегом, зависящим от id
async function getPost(id) {
const res = await fetch(`https://api.example.com/posts/${id}`, {
next: { tags: [`post-${id}`] },
});
return res.json();
}
// При редактировании поста:
await fetch(`https://api.example.com/posts/${id}`, { ... });
revalidateTag(`post-${id}`); // Сбросить кеш ТОЛЬКО для этого поста
Советы и нюансы
- Имена тегов — это просто строки, вы можете использовать любые осмысленные значения: 'tasks', 'products', 'user-123' и т.д.
- Один запрос — несколько тегов: Можно передать массив тегов: tags: ['tasks', 'dashboard'].
- Теги не глобальны: Они работают только в рамках вашего приложения.
- Массовый сброс: Если нужно сбросить кеш сразу для нескольких сущностей, можно вызвать revalidateTag() несколько раз или использовать общий тег.
Визуальная схема работы tags и revalidateTag()
graph LR
A[fetch с тегом 'tasks'] -- кешируется --> B[Кеш с тегом 'tasks']
B -- revalidateTag('tasks') --> C[Кеш сброшен]
C -- следующий fetch --> D[Получены свежие данные]
4. Практика: реализуем сброс кеша при удалении задачи
Допустим, вы реализовали удаление задачи. После удаления нужно сбросить кеш списка задач.
// app/tasks/actions.ts
'use server';
import { revalidateTag } from 'next/cache';
export async function deleteTask(id: string) {
await fetch(`https://api.example.com/tasks/${id}`, {
method: 'DELETE',
});
revalidateTag('tasks');
}
Теперь после удаления задачи при следующем заходе на /tasks или переходе на страницу, где используется тег 'tasks', пользователь увидит актуальный список.
5. Типичные ошибки при работе с tags и revalidateTag()
Ошибка №1: Использование revalidateTag на клиенте.
Функция revalidateTag() работает только на сервере. Если попытаться вызвать её в Client Component — получите ошибку: "revalidateTag can only be called on the server".
Ошибка №2: Забыли добавить тег в fetch.
Если вы не указали тег в опциях fetch, вызов revalidateTag('tasks') ничего не даст — кеш не сбросится, потому что данных с этим тегом нет. Всегда проверяйте, что fetch и revalidateTag используют одинаковые имена тегов.
Ошибка №3: Слишком "широкие" теги.
Если вы используете один и тот же тег для слишком большого объёма данных (например, 'all'), то при сбросе кеша будете обновлять всё приложение. Лучше использовать более "узкие" и осмысленные теги.
Ошибка №4: Ожидание мгновенного обновления на клиенте.
Если вы используете revalidateTag и надеетесь, что у всех пользователей мгновенно обновятся данные без перезагрузки страницы — это не так. Кеш сбрасывается для следующих fetch-запросов, но уже открытые страницы сами не обновятся. Используйте навигацию или обновление страницы для получения новых данных.
Ошибка №5: Несовпадение тегов в fetch и revalidateTag.
Если где-то в fetch указали tags: ['task'], а сбрасываете через revalidateTag('tasks') (с буквой "s") — кеш не сбросится. Следите за именами!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ