Пошук уроків, статей та іншого контенту
Налаштуєте отримання актуальних даних, кешування та повторну валідацію для динамічних сторінок.
Динамічні дані — це дані, які можуть змінюватися між запитами: список товарів, повідомлення, профіль користувача або результати пошуку.
У Next.js дані можна отримувати на сервері безпосередньо в Server Component. Це дає змогу:
не передавати зайву логіку отримання даних у браузер;
приховати секретні ключі API;
контролювати кешування;
визначати, коли сторінка має повторно отримувати дані.
Приклад сторінки, яка отримує дані на сервері:
// app/products/page.tsx
type Product = {
id: number;
title: string;
price: number;
};
async function getProducts(): Promise<Product[]> {
const response = await fetch("https://api.example.com/products", {
cache: "no-store",
});
if (!response.ok) {
throw new Error("Не вдалося отримати товари");
}
return response.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<main>
<h1>Товари</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.title} — {product.price} грн
</li>
))}
</ul>
</main>
);
}Оскільки компонент асинхронний, він очікує завершення fetch до формування відповіді.
fetchУ Next.js кешування для fetch потрібно налаштовувати явно, якщо поведінка кешу важлива для сторінки. Основні варіанти:
cache: "no-store"Запит виконується під час кожного звернення до сторінки:
const response = await fetch("https://api.example.com/products", {
cache: "no-store",
});Цей режим підходить для:
даних, які мають бути максимально актуальними;
персоналізованого вмісту;
даних, що часто змінюються;
результатів, залежних від сесії або авторизованого користувача.
У такому разі Next.js не використовує збережену відповідь для цього запиту.
cache: "force-cache"Результат запиту може зберігатися в кеші:
const response = await fetch("https://api.example.com/categories", {
cache: "force-cache",
});Це доречно для даних, які змінюються рідко, наприклад:
списку категорій;
конфігурації сайту;
довідкової інформації;
публічного контенту, який не потребує миттєвого оновлення.
Кешування не замінює перевірку помилок HTTP. Метод fetch не викидає помилку автоматично для статусів 404 або 500, тому потрібно перевіряти response.ok:
if (!response.ok) {
throw new Error(`Помилка API: ${response.status}`);
}Часто немає потреби отримувати найновіші дані під час кожного запиту. Наприклад, каталог можна оновлювати один раз на хвилину.
Для цього використовується параметр next.revalidate:
async function getProducts() {
const response = await fetch("https://api.example.com/products", {
next: {
revalidate: 60,
},
});
if (!response.ok) {
throw new Error("Не вдалося отримати товари");
}
return response.json();
}revalidate: 60 означає, що кешована відповідь може використовуватися протягом 60 секунд. Після цього Next.js повторно отримає дані та оновить кеш.
Цей підхід називається Incremental Static Regeneration, або ISR.
Він корисний, коли:
дані змінюються періодично;
не потрібне оновлення на кожен запит;
важлива швидка відповідь;
сторінка має залишатися доступною навіть між оновленнями даних.
Значення вказується в секундах:
next: {
revalidate: 10, // оновлення приблизно кожні 10 секунд
}// app/news/page.tsx
type NewsItem = {
id: number;
title: string;
description: string;
};
async function getNews(): Promise<NewsItem[]> {
const response = await fetch("https://api.example.com/news", {
next: {
revalidate: 300,
tags: ["news"],
},
});
if (!response.ok) {
throw new Error("Не вдалося отримати новини");
}
return response.json();
}
export default async function NewsPage() {
const news = await getNews();
return (
<main>
<h1>Останні новини</h1>
{news.length === 0 ? (
<p>Новин поки немає.</p>
) : (
<ul>
{news.map((item) => (
<li key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</li>
))}
</ul>
)}
</main>
);
}У цьому прикладі:
дані кешуються;
кеш може оновлюватися кожні 300 секунд;
запит отримує тег news;
сторінка залишається серверним компонентом.
Для сторінки можна вказати інтервал повторної валідації на рівні файлу:
// app/news/page.tsx
export const revalidate = 300;
export default async function NewsPage() {
const response = await fetch("https://api.example.com/news");
if (!response.ok) {
throw new Error("Не вдалося отримати новини");
}
const news = await response.json();
return (
<main>
<h1>Новини</h1>
<ul>
{news.map((item: { id: number; title: string }) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</main>
);
}export const revalidate = 300 задає повторну валідацію для маршруту. Проте для критично важливих запитів краще явно вказувати параметри в самому fetch, щоб поведінка кожного запиту була зрозумілою.
Сторінка може бути примусово зроблена динамічною:
// app/dashboard/page.tsx
export const dynamic = "force-dynamic";
async function getDashboardData() {
const response = await fetch("https://api.example.com/dashboard", {
cache: "no-store",
});
if (!response.ok) {
throw new Error("Не вдалося отримати дані панелі");
}
return response.json();
}
export default async function DashboardPage() {
const data = await getDashboardData();
return (
<main>
<h1>Панель керування</h1>
<p>Замовлень сьогодні: {data.ordersToday}</p>
</main>
);
}dynamic = "force-dynamic" вказує Next.js, що маршрут потрібно рендерити динамічно на сервері для кожного запиту.
Зазвичай разом із цим використовують cache: "no-store", якщо зовнішні дані також не повинні кешуватися.
Іноді чекати завершення інтервалу валідації не потрібно. Наприклад, адміністратор змінив новину, і кеш потрібно оновити одразу.
Для цього запиту призначають тег:
const response = await fetch("https://api.example.com/news", {
next: {
revalidate: 300,
tags: ["news"],
},
});Після зміни даних можна викликати revalidateTag у Server Action або Route Handler:
// app/admin/news/actions.ts
"use server";
import { revalidateTag } from "next/cache";
export async function updateNews() {
// Тут відбувається оновлення новини в базі даних або зовнішньому API.
revalidateTag("news");
}Після revalidateTag("news") кешовані запити з тегом news вважаються застарілими. Наступне отримання цих даних виконає новий запит.
Тег потрібно призначати саме під час виклику fetch. Він не працює автоматично для всіх запитів сторінки.
Якщо потрібно повторно згенерувати конкретну сторінку, використовується revalidatePath:
// app/admin/news/actions.ts
"use server";
import { revalidatePath } from "next/cache";
export async function saveNews() {
// Тут зберігаємо новину.
revalidatePath("/news");
}Після цього кеш маршруту /news буде визнано застарілим.
Вибір між функціями:
revalidateTag("news") — оновлює дані, пов’язані з певним тегом;
revalidatePath("/news") — оновлює кеш конкретного маршруту.
Якщо fetch завершується помилкою, серверний компонент може викинути помилку:
async function getProfile() {
const response = await fetch("https://api.example.com/profile", {
cache: "no-store",
});
if (!response.ok) {
throw new Error("Не вдалося завантажити профіль");
}
return response.json();
}Для користувача варто передбачити окремий файл error.tsx у відповідному сегменті маршруту:
// app/profile/error.tsx
"use client";
type ErrorProps = {
reset: () => void;
};
export default function ProfileError({ reset }: ErrorProps) {
return (
<main>
<h1>Сталася помилка</h1>
<p>Профіль тимчасово недоступний.</p>
<button onClick={() => reset()}>
Спробувати ще раз
</button>
</main>
);
}Компонент помилки має бути клієнтським, тому що обробник onClick виконується в браузері.
Використовуйте no-store, якщо:
дані повинні бути актуальними під час кожного запиту;
дані залежать від конкретного користувача;
відповідь не можна безпечно зберігати в кеші.
Використовуйте revalidate, якщо:
дані змінюються, але не щосекунди;
невелика затримка оновлення прийнятна;
важлива швидка відповідь для повторних запитів.
Використовуйте теговане кешування, якщо:
дані оновлюються за певною подією;
після редагування потрібно очистити пов’язані записи;
один набір даних використовується на кількох сторінках.
response.okconst data = await response.json();Такий код може намагатися розібрати як JSON навіть відповідь із помилкою. Перевіряйте статус перед читанням даних.
Не використовуйте кеш для даних, які залежать від користувача, якщо відповідь може бути показана іншому користувачеві.
Для таких запитів використовуйте:
fetch(url, {
cache: "no-store",
});revalidaterevalidate: 60 не означає, що дані оновлюватимуться рівно кожні 60 секунд незалежно від запитів. Це інтервал, після якого кеш може бути повторно перевірений під час наступного звернення.
Якщо дані потрібно оновити одразу після операції, використовуйте revalidateTag або revalidatePath.
Сторінка може містити кілька запитів із різними вимогами:
const categoriesResponse = await fetch(
"https://api.example.com/categories",
{
next: { revalidate: 3600 },
},
);
const notificationsResponse = await fetch(
"https://api.example.com/notifications",
{
cache: "no-store",
},
);Категорії можна оновлювати раз на годину, а сповіщення отримувати під час кожного запиту.
Дані в Server Component можна отримувати за допомогою асинхронного fetch.
cache: "no-store" виконує запит щоразу та підходить для актуальних або персональних даних.
next.revalidate задає повторну валідацію кешу через певний інтервал.
dynamic = "force-dynamic" примусово вмикає динамічний рендеринг маршруту.
next.tags дає змогу пов’язати запит із тегом.
revalidateTag оновлює кеш за тегом, а revalidatePath — кеш конкретного маршруту.
Для надійного отримання даних потрібно перевіряти response.ok і передбачати обробку помилок.