Пошук уроків, статей та іншого контенту
Оновите кешовані дані після мутації за допомогою revalidatePath і revalidateTag.
Після мутації даних — створення, редагування або видалення запису — кеш Next.js може все ще містити стару версію сторінки чи результату запиту.
Наприклад:
користувач відкрив список завдань;
список було закешовано;
користувач додав нове завдання;
наступний рендер може знову отримати старий список із кешу.
Щоб повідомити Next.js про зміну даних, після успішної мутації потрібно інвалідувати кеш:
revalidatePath — для конкретного маршруту;
revalidateTag — для даних, позначених певним тегом.
Обидві функції викликаються на сервері: у Server Action або Route Handler.
revalidatePathrevalidatePath інвалідує кеш, пов’язаний із вказаним маршрутом.
import { revalidatePath } from "next/cache";
revalidatePath("/");Після цього Next.js повторно сформує сторінку під час наступного звернення до маршруту.
Для динамічних маршрутів можна вказати тип маршруту:
revalidatePath("/posts/[slug]", "page");Другий аргумент може бути:
"page" — інвалідувати сторінку;
"layout" — інвалідувати layout і вкладені сторінки.
Якщо потрібно оновити конкретний URL, можна передати фактичний шлях:
revalidatePath("/posts/42");revalidateTagrevalidateTag працює з кешованими даними, яким було призначено тег.
Для fetch тег задається через next.tags:
const response = await fetch("https://example.com/api/posts", {
next: {
tags: ["posts"],
},
});Після мутації цей тег можна інвалідувати:
import { revalidateTag } from "next/cache";
revalidateTag("posts", "max");Профіль "max" використовує stale-while-revalidate: кешовані дані можуть бути показані під час першого запиту, а Next.js паралельно отримає свіжі дані для наступних запитів.
Назва тегу при читанні та інвалідації має збігатися точно:
// Читання
next: {
tags: ["posts"],
}
// Інвалідація
revalidateTag("posts", "max");Розглянемо сторінку зі списком завдань. Для демонстрації використаємо кешування через unstable_cache. У реальному застосунку функції роботи зі сховищем зазвичай взаємодіють із базою даних.
Файл lib/todos.ts:
import { unstable_cache } from "next/cache";
type Todo = {
id: number;
title: string;
};
// Демонстраційне сховище в пам'яті процесу.
let todos: Todo[] = [
{ id: 1, title: "Вивчити Server Actions" },
{ id: 2, title: "Додати інвалідацію кешу" },
];
export const getTodos = unstable_cache(
async () => todos,
["todos-list"],
{
tags: ["todos"],
},
);
export async function createTodo(title: string) {
const todo: Todo = {
id: Date.now(),
title,
};
// Створюємо новий масив, не змінюючи закешований результат напряму.
todos = [...todos, todo];
}getTodos кешує результат і прив’язує його до тегу "todos".
Файл app/actions.ts:
"use server";
import { revalidatePath, revalidateTag } from "next/cache";
import { createTodo } from "@/lib/todos";
export async function addTodo(formData: FormData) {
const value = formData.get("title");
if (typeof value !== "string") {
throw new Error("Некоректне значення title");
}
const title = value.trim();
if (!title) {
throw new Error("Назва завдання не може бути порожньою");
}
await createTodo(title);
// Позначаємо кешовані дані зі старим списком як неактуальні.
revalidateTag("todos", "max");
// Інвалідуємо кеш сторінки, яка показує цей список.
revalidatePath("/", "page");
}Порядок операцій важливий:
виконати мутацію;
інвалідувати пов’язані теги;
інвалідувати сторінки, які відображають змінені дані.
Якщо викликати revalidation до мутації, Next.js може повторно закешувати ще старий результат.
Файл app/page.tsx:
import { addTodo } from "./actions";
import { getTodos } from "@/lib/todos";
export default async function HomePage() {
const todos = await getTodos();
return (
<main>
<h1>Список завдань</h1>
<form action={addTodo}>
<label htmlFor="title">Нове завдання</label>
<input id="title" name="title" required />
<button type="submit">Додати</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</main>
);
}Після надсилання форми:
Server Action отримує дані;
створює новий запис;
викликає revalidateTag("todos", "max");
викликає revalidatePath("/", "page");
сторінка отримує актуальний список під час наступного рендеру.
revalidatePath, колиПотрібно оновити конкретну сторінку після мутації:
revalidatePath("/dashboard");Це зручно, коли ви точно знаєте, які маршрути відображають змінені дані.
revalidateTag, колиОдні й ті самі дані використовуються в кількох сторінках або компонентах:
revalidateTag("products", "max");У такому випадку не потрібно перелічувати всі маршрути. Достатньо позначити запит тегом "products" і після мутації інвалідувати цей тег.
Це поширений варіант для мутацій:
await updateProduct(productId, data);
revalidateTag("products", "max");
revalidatePath("/products", "page");
revalidatePath(`/products/${productId}`);Тег інвалідує кеш даних, а revalidatePath явно оновлює маршрути, які мають показати результат мутації.
Такий самий підхід застосовується для видалення:
"use server";
import { revalidatePath, revalidateTag } from "next/cache";
import { deleteTodo } from "@/lib/todos";
export async function removeTodo(formData: FormData) {
const value = formData.get("id");
if (typeof value !== "string") {
throw new Error("Некоректний ідентифікатор");
}
await deleteTodo(Number(value));
revalidateTag("todos", "max");
revalidatePath("/", "page");
}Інвалідацію потрібно виконувати лише після успішного завершення операції. Якщо видалення завершилося помилкою, кеш не слід оновлювати як ніби дані змінилися.
revalidatePath і revalidateTag також можна викликати в Route Handler:
import { NextResponse } from "next/server";
import { revalidatePath, revalidateTag } from "next/cache";
import { createTodo } from "@/lib/todos";
export async function POST(request: Request) {
const body = await request.json();
const title = typeof body.title === "string" ? body.title.trim() : "";
if (!title) {
return NextResponse.json(
{ error: "Назва завдання є обов'язковою" },
{ status: 400 },
);
}
await createTodo(title);
revalidateTag("todos", "max");
revalidatePath("/", "page");
return NextResponse.json({ success: true });
}Цей підхід корисний, коли мутація надходить не через форму Server Action, а через HTTP-запит.
Це неправильно:
"use client";
import { revalidatePath } from "next/cache";revalidatePath і revalidateTag призначені для серверного коду. Використовуйте їх у Server Action або Route Handler.
Якщо дані кешуються з тегом "todos", виклик із "todo" не матиме потрібного ефекту:
// Кешований запит
next: {
tags: ["todos"],
}
// Правильна інвалідація
revalidateTag("todos", "max");revalidatePath("/todos");
await updateTodos();У такому випадку повторне кешування може відбутися до завершення оновлення даних. Спочатку виконуйте мутацію, а потім інвалідуйте кеш.
revalidateTag оновить будь-яку сторінкуТег пов’язаний лише з кешованими даними, які явно отримали цей тег. Якщо сторінка не використовує запит із тегом "todos" або кешовану функцію з цим тегом, revalidateTag("todos", "max") не вплине на неї.
Масив у прикладі потрібен лише для демонстрації. У production-середовищі дані в пам’яті процесу:
можуть зникнути після перезапуску;
можуть бути різними на різних інстансах;
не замінюють базу даних.
Механізм revalidation залишається таким самим: після запису в постійне сховище потрібно інвалідувати відповідні теги та маршрути.
revalidatePath інвалідує кеш конкретного маршруту.
revalidateTag інвалідує кешовані дані за тегом.
Тег потрібно додати під час отримання даних і використати з тією самою назвою після мутації.
Revalidation виконується тільки на сервері.
Спочатку потрібно успішно виконати мутацію, а потім викликати revalidateTag і revalidatePath.
Для сторінок, які відображають змінені дані, часто зручно використовувати обидва методи.