JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Использование tags для кеширования,

Использование tags для кеширования, revalidateTag() в Next.js 15

Модуль 4: Node.js, Next.js и Angular
9 уровень , 6 лекция
Открыта

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, которая:

  1. Добавляет задачу в базу.
  2. Вызывает revalidateTag('tasks').
  3. Next.js сбрасывает кеш для всех fetch-запросов с тегом 'tasks'.
  4. При следующем открытии страницы (или навигации) данные будут свежими!

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[Получены свежие данные]

Схема работы кеша с тегами и revalidateTag()

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") — кеш не сбросится. Следите за именами!

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ