Пошук уроків, статей та іншого контенту
Реалізуєте ручне очищення або оновлення кешу після змін у CMS чи базі даних.
У Next.js дані можуть кешуватися під час виконання fetch, а сторінки — повторно використовувати вже згенерований результат. Це зменшує кількість запитів до CMS або бази даних і прискорює відповіді.
Проблема виникає після зміни даних:
редактор змінює статтю в CMS;
CMS надсилає webhook у Next.js;
Next.js очищає пов’язані кешовані дані;
наступний запит отримує актуальну версію.
Таке ручне очищення або оновлення кешу називається on-demand revalidation.
Основні функції для цього:
revalidatePath() — повторно валідує сторінку або маршрут;
revalidateTag() — очищає кеш усіх запитів із певним тегом.
Обидві функції потрібно викликати на сервері:
у Server Action;
у Route Handler;
в іншому серверному коді Next.js.
revalidatePathrevalidatePath() приймає шлях, кеш якого потрібно повторно валідувати.
import { revalidatePath } from 'next/cache';
revalidatePath('/blog');Після цього Next.js не використовуватиме стару кешовану версію для /blog під час наступного запиту.
Можна також вказати тип маршруту:
revalidatePath('/blog/[slug]', 'page');Другий аргумент може бути:
'page' — для сторінки;
'layout' — для layout;
не вказаний — для конкретного шляху.
Найчастіше для ручного оновлення конкретної сторінки достатньо:
revalidatePath(`/blog/${slug}`);revalidateTagЯкщо кілька сторінок використовують одні й ті самі дані, зручніше прив’язати запит до тегу.
fetchconst response = await fetch('https://cms.example.com/posts', {
cache: 'force-cache',
next: {
tags: ['posts'],
},
});
const posts = await response.json();Тепер цей запит належить до групи кешу з тегом posts.
import { revalidateTag } from 'next/cache';
revalidateTag('posts', 'max');Після цього всі кешовані запити з тегом posts вважаються застарілими.
Профіль 'max' використовує модель stale-while-revalidate:
користувач може тимчасово отримати старі дані;
Next.js запускає оновлення кешу;
наступні запити отримують свіжі дані.
Це зручно для даних, які не повинні блокувати відповідь користувачеві.
Розглянемо сторінку блогу, яка отримує список статей із CMS.
Файл lib/posts.ts:
type Post = {
id: string;
title: string;
slug: string;
};
export async function getPosts(): Promise<Post[]> {
const cmsUrl = process.env.CMS_URL;
if (!cmsUrl) {
throw new Error('CMS_URL не налаштовано');
}
const response = await fetch(`${cmsUrl}/posts`, {
cache: 'force-cache',
next: {
tags: ['posts'],
},
});
if (!response.ok) {
throw new Error('Не вдалося отримати статті');
}
return response.json();
}Файл app/blog/page.tsx:
import { getPosts } from '@/lib/posts';
export default async function BlogPage() {
const posts = await getPosts();
return (
<main>
<h1>Блог</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
</main>
);
}Поки кеш актуальний, Next.js може не виконувати повторний запит до CMS для кожного відвідувача.
CMS може надіслати POST-запит до спеціального маршруту Next.js після створення або редагування статті.
Файл app/api/revalidate/route.ts:
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = process.env.REVALIDATE_SECRET;
const authorization = request.headers.get('authorization');
if (!secret || authorization !== `Bearer ${secret}`) {
return NextResponse.json(
{ message: 'Недійсні облікові дані' },
{ status: 401 },
);
}
try {
const body = await request.json();
revalidateTag('posts', 'max');
revalidatePath('/blog');
if (body.slug) {
revalidatePath(`/blog/${body.slug}`);
}
return NextResponse.json({
revalidated: true,
message: 'Кеш блогу очищено',
});
} catch {
return NextResponse.json(
{ message: 'Некоректне тіло запиту' },
{ status: 400 },
);
}
}Тепер CMS має надіслати запит приблизно такого вигляду:
POST /api/revalidate
Authorization: Bearer your-secret
Content-Type: application/json{
"slug": "nextjs-caching"
}Після цього:
кеш запитів із тегом posts буде повторно валідований;
сторінка /blog буде повторно валідована;
сторінка конкретної статті також буде повторно валідована.
Значення секрету потрібно зберігати в змінних середовища:
CMS_URL=https://cms.example.com
REVALIDATE_SECRET=your-secretСекрет у CMS має збігатися зі значенням REVALIDATE_SECRET.
revalidatePath чи revalidateTagВибір залежить від того, що саме потрібно оновити.
revalidatePath, якщо:потрібно оновити одну сторінку;
змінився результат конкретного маршруту;
сторінка має дані з кількох джерел;
ви не контролюєте або не хочете змінювати всі fetch-запити.
revalidatePath('/products');revalidateTag, якщо:одні дані використовуються на багатьох сторінках;
потрібно очистити групу пов’язаних запитів;
у коді вже визначені логічні теги кешу.
revalidateTag('products', 'max');На практиці можна використовувати обидва підходи разом:
revalidateTag('posts', 'max');
revalidatePath('/blog');
revalidatePath(`/blog/${slug}`);Теги очищають кеш даних, а revalidatePath явно позначає сторінки, які потрібно оновити.
On-demand revalidation необов’язково запускати лише через webhook. Його можна викликати після зміни даних у Server Action.
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
export async function updatePost(slug: string, title: string) {
// Тут відбувається оновлення статті в базі даних.
await updatePostInDatabase(slug, title);
revalidateTag('posts', 'max');
revalidatePath('/blog');
revalidatePath(`/blog/${slug}`);
}
async function updatePostInDatabase(slug: string, title: string) {
// Демонстраційна функція замість реального запиту до бази даних.
console.log(`Оновлено статтю ${slug}: ${title}`);
}У реальному застосунку updatePostInDatabase має виконувати запит до бази даних. Важливий порядок:
спочатку успішно змінити дані;
потім викликати revalidateTag() або revalidatePath().
Якщо очистити кеш до невдалого оновлення бази даних, наступний запит знову збереже старі або некоректні дані.
Виклик із Route Handler і виклик із Server Action мають важливу відмінність у поведінці інтерфейсу.
У Server Action Next.js може оновити стан поточної сторінки після завершення дії. У Route Handler кеш позначається як застарілий, а актуальні дані будуть отримані під час наступного запиту до маршруту.
Це особливо підходить для webhook:
import { revalidatePath } from 'next/cache';
import { NextResponse } from 'next/server';
export async function POST() {
revalidatePath('/blog');
return NextResponse.json({ revalidated: true });
}Webhook не повинен намагатися повертати оновлену сторінку CMS. Його завдання — перевірити запит і позначити кеш для оновлення.
On-demand revalidation працює лише тоді, коли є що валідувати. Якщо дані отримуються з cache: 'no-store', такий fetch не має кешу, який можна очистити.
const response = await fetch(url, {
cache: 'no-store',
});У цьому випадку кожен запит одразу звертається до джерела даних, тому revalidateTag() для нього не дає переваги.
Також повторна валідація не змінює дані в CMS або базі даних. Вона лише повідомляє Next.js, що кешовані результати більше не можна вважати актуальними.
Повний сценарій виглядає так:
Користувач або редактор змінює статтю в CMS.
CMS успішно зберігає зміни.
CMS надсилає захищений webhook у /api/revalidate.
Route Handler перевіряє секрет.
Route Handler викликає revalidateTag() і, за потреби, revalidatePath().
Наступний запит отримує свіжі дані з CMS.
Оновлений результат знову може бути збережений у кеші.
Функції з next/cache призначені для серверного коду. Не викликайте їх у компоненті з 'use client'.
Неправильно:
'use client';
import { revalidatePath } from 'next/cache';Виклик потрібно перенести в Server Action або Route Handler.
fetchВиклик:
revalidateTag('posts', 'max');не вплине на запит, який не має такого тегу:
fetch(url, {
cache: 'force-cache',
});Потрібно додати тег під час отримання даних:
fetch(url, {
cache: 'force-cache',
next: {
tags: ['posts'],
},
});Спочатку виконайте операцію запису, перевірте її результат, а вже потім очищуйте кеш.
Route Handler для revalidation може змінювати поведінку кешу всього застосунку. Не залишайте його без автентифікації.
Перевіряйте:
секретний заголовок;
формат запиту;
за потреби — джерело webhook або підпис запиту.
Шлях у revalidatePath() має відповідати маршруту Next.js:
revalidatePath('/blog');Це не те саме, що URL CMS або API-ендпоїнт:
revalidatePath('https://cms.example.com/posts');On-demand revalidation дає змогу оновлювати кеш одразу після змін у CMS або базі даних.
revalidatePath() використовується для повторної валідації конкретного маршруту.
revalidateTag() використовується для групи кешованих запитів.
Тег додається під час fetch через next.tags.
Revalidation потрібно запускати на сервері — у Server Action або Route Handler.
Для webhook потрібно перевіряти секрет або підпис запиту.
Спочатку змінюйте дані, а потім очищуйте пов’язаний кеш.
Запити з cache: 'no-store' не мають кешу для повторної валідації.