Пошук уроків, статей та іншого контенту
Розберете Cache Components і поєднаєте кешовані частини інтерфейсу з динамічним виконанням.
Cache Components — це модель кешування в Next.js, яка дає змогу поєднати в одному маршруті:
статичну оболонку сторінки;
кешовані компоненти з терміном життя;
динамічні компоненти, які виконуються під час кожного запиту.
Без Cache Components використання динамічних API, наприклад cookies() або headers(), могло вплинути на кешування всього маршруту. У новій моделі динамічною стає лише та частина дерева компонентів, яка справді залежить від даних запиту.
Це особливо корисно для сторінок, де:
список товарів змінюється нечасто;
привітання залежить від cookie користувача;
оболонка сторінки має відображатися одразу;
повільні частини інтерфейсу можна завантажити окремо.
У Next.js 16 Cache Components вмикаються в конфігурації проєкту:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;У версіях Next.js, де ця можливість ще має експериментальний статус, використовується конфігурація:
const nextConfig = {
experimental: {
cacheComponents: true,
},
};
export default nextConfig;Директива "use cache" доступна лише після ввімкнення Cache Components.
"use cache"Директиву "use cache" можна додати:
на початку компонента;
на початку асинхронної функції;
на початку модуля.
Найчастіше зручно кешувати окрему функцію, яка отримує або готує дані:
async function getProducts() {
"use cache";
return [
{ id: 1, name: "Keyboard", price: 120 },
{ id: 2, name: "Mouse", price: 60 },
];
}Результат функції кешується, а наступні виклики можуть використовувати вже готове значення замість повторного виконання функції.
Ключ кешу формується на основі:
самої функції;
її аргументів;
значень, які вона використовує із зовнішньої області видимості.
Тому кешована функція має отримувати змінні дані через аргументи, а не читати їх безпосередньо з динамічного контексту запиту.
cacheLifeДля налаштування тривалості кешу використовується cacheLife з пакета next/cache.
import { cacheLife } from "next/cache";
async function getProducts() {
"use cache";
cacheLife("hours");
return [
{ id: 1, name: "Keyboard", price: 120 },
{ id: 2, name: "Mouse", price: 60 },
];
}Профіль "hours" означає, що результат призначений для кешування протягом тривалого часу. Також можна передати власну конфігурацію:
import { cacheLife } from "next/cache";
async function getProducts() {
"use cache";
cacheLife({
revalidate: 300,
expire: 3600,
});
return loadProducts();
}У цьому прикладі:
revalidate: 300 — результат можна оновити через 300 секунд;
expire: 3600 — запис вважається остаточно застарілим через 3600 секунд.
Точна стратегія оновлення залежить від конфігурації та середовища виконання, але загальна ідея така: кешований результат не потрібно створювати заново під час кожного запиту.
cacheTagЩоб інвалідовувати пов’язані кешовані значення, їм можна призначити тег:
import { cacheLife, cacheTag } from "next/cache";
async function getProducts() {
"use cache";
cacheLife("hours");
cacheTag("products");
return loadProducts();
}Тепер усі кешовані значення з тегом "products" можна позначити як такі, що потребують оновлення:
import { revalidateTag } from "next/cache";
export async function POST() {
revalidateTag("products", "max");
return Response.json({ revalidated: true });
}Після цього наступне звернення до кешованої функції отримає оновлені дані.
cacheTag потрібно додавати всередині функції або компонента, де вже використовується "use cache".
Динамічний компонент не повинен використовувати "use cache", якщо він читає дані поточного запиту.
Наприклад, cookies() повертає cookie конкретного користувача:
import { cookies } from "next/headers";
export async function UserGreeting() {
const cookieStore = await cookies();
const name = cookieStore.get("name")?.value ?? "гість";
return <p>Вітаємо, {name}!</p>;
}Цей компонент виконується динамічно. Його результат не можна спільно кешувати для всіх користувачів, оскільки в кожного користувача може бути інше значення cookie.
Динамічну частину потрібно розміщувати всередині Suspense. Це дає змогу показати оболонку сторінки та резервний вміст, поки динамічний компонент виконується.
Розглянемо повний приклад.
// app/products/ProductList.tsx
import { cacheLife, cacheTag } from "next/cache";
type Product = {
id: number;
name: string;
price: number;
};
async function loadProducts(): Promise<Product[]> {
// Імітація повільного звернення до бази даних
await new Promise((resolve) => setTimeout(resolve, 1000));
return [
{ id: 1, name: "Keyboard", price: 120 },
{ id: 2, name: "Mouse", price: 60 },
{ id: 3, name: "Monitor", price: 350 },
];
}
async function getProducts() {
"use cache";
cacheLife("hours");
cacheTag("products");
return loadProducts();
}
export async function ProductList() {
const products = await getProducts();
return (
<section>
<h2>Товари</h2>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name}: ${product.price}
</li>
))}
</ul>
</section>
);
}getProducts кешує результат loadProducts. Після першого виконання інші запити можуть використовувати кешовані дані.
// app/UserGreeting.tsx
import { cookies } from "next/headers";
export async function UserGreeting() {
const cookieStore = await cookies();
const name = cookieStore.get("name")?.value ?? "гість";
return <p>Вітаємо, {name}!</p>;
}// app/page.tsx
import { Suspense } from "react";
import { ProductList } from "./products/ProductList";
import { UserGreeting } from "./UserGreeting";
function ProductListFallback() {
return <p>Завантаження товарів...</p>;
}
function GreetingFallback() {
return <p>Завантаження привітання...</p>;
}
export default function HomePage() {
return (
<main>
<h1>Магазин</h1>
<Suspense fallback={<GreetingFallback />}>
<UserGreeting />
</Suspense>
<Suspense fallback={<ProductListFallback />}>
<ProductList />
</Suspense>
</main>
);
}У цьому прикладі:
h1 є частиною оболонки сторінки;
ProductList використовує кешовані дані;
UserGreeting читає cookie та виконується динамічно;
кожна асинхронна частина має власний Suspense;
повільне завантаження товарів не блокує відображення всієї сторінки.
Кешована функція може приймати аргументи. Це дає змогу створювати окремі кешовані записи для різних значень.
import { cacheLife, cacheTag } from "next/cache";
type Product = {
id: number;
name: string;
category: string;
};
async function getProductsByCategory(category: string) {
"use cache";
cacheLife("hours");
cacheTag(`products:${category}`);
const products: Product[] = [
{ id: 1, name: "Keyboard", category: "accessories" },
{ id: 2, name: "Mouse", category: "accessories" },
{ id: 3, name: "Monitor", category: "screens" },
];
return products.filter((product) => product.category === category);
}Виклики з різними аргументами кешуються окремо:
const accessories = await getProductsByCategory("accessories");
const screens = await getProductsByCategory("screens");У такому випадку:
"accessories" має власний запис кешу;
"screens" має інший запис кешу;
зміна аргументу змінює ключ кешу.
Це важливо для сторінок категорій, профілів, локалізованого контенту та інших маршрутів із параметрами.
Кешований компонент не повинен безпосередньо викликати динамічні API:
import { cookies } from "next/headers";
export async function CachedHeader() {
"use cache";
const cookieStore = await cookies();
return <header>{cookieStore.get("name")?.value}</header>;
}Такий підхід неправильний, тому що кешований результат може бути повторно використаний для іншого користувача.
Правильніше розділити відповідальності:
export function Header() {
return (
<header>
<Logo />
<UserGreeting />
</header>
);
}Де:
Logo може бути статичним або кешованим;
UserGreeting залишається динамічним;
кожна частина має власну стратегію виконання.
Кешування потрібно застосовувати лише до результату, який безпечно повторно використовувати для різних запитів.
Якщо дані змінилися, кешований результат потрібно інвалідовувати.
Наприклад, маршрут для оновлення товарів:
// app/api/products/revalidate/route.ts
import { revalidateTag } from "next/cache";
export async function POST() {
revalidateTag("products", "max");
return Response.json({
message: "Кеш товарів позначено для оновлення",
});
}Функція, яка завантажує товари, повинна використовувати такий самий тег:
import { cacheLife, cacheTag } from "next/cache";
async function getProducts() {
"use cache";
cacheLife("hours");
cacheTag("products");
return loadProducts();
}Тег є зв’язком між:
даними, які кешуються;
операцією, яка повідомляє Next.js про їхню зміну.
Для різних наборів даних краще використовувати різні теги, наприклад:
products;
products:accessories;
products:42.
Cache Components добре підходять, коли сторінка має змішану структуру:
заголовок і навігація однакові для всіх;
каталог можна кешувати;
профіль користувача залежить від cookie;
рекомендації завантажуються окремо;
частина даних має оновлюватися частіше за іншу.
Перед кешуванням варто перевірити:
Чи однаковий результат можна показувати різним користувачам?
Як довго результат залишається актуальним?
Який тег потрібен для інвалідації?
Чи використовує функція cookies(), headers() або інші дані поточного запиту?
Чи має повільна динамічна частина власний Suspense?
Неправильно:
import { cookies } from "next/headers";
async function getUserData() {
"use cache";
const cookieStore = await cookies();
return cookieStore.get("userId")?.value;
}Cookie належать конкретному запиту, тому результат цієї функції не можна спільно кешувати.
Краще прочитати cookie в динамічному компоненті, а безпечні для кешування дані передати в окрему функцію аргументом.
Suspense для динамічної частиниДинамічний компонент може затримати частину інтерфейсу. Обгорніть його в Suspense і визначте fallback:
<Suspense fallback={<p>Завантаження...</p>}>
<UserGreeting />
</Suspense>Якщо функція використовує cacheTag("products"), інвалідація тегом "product" не вплине на її кеш.
Назви тегів повинні збігатися точно.
Не слід використовувати тривалий профіль для даних, які змінюються майже в реальному часі. Оберіть коротший cacheLife або інвалідуйте тег після кожної зміни даних.
Результат, що залежить від:
cookie;
заголовків запиту;
ідентифікатора поточного користувача;
іншого запитозалежного контексту,
не можна бездумно кешувати як спільний результат для всіх користувачів.
Cache Components дають змогу поєднувати кешовані та динамічні частини одного маршруту.
"use cache" кешує результат компонента або функції.
cacheLife визначає політику часу життя кешу.
cacheTag дозволяє групувати кешовані дані.
revalidateTag позначає дані з певним тегом для оновлення.
Динамічні API на кшталт cookies() потрібно використовувати поза кешованими функціями.
Динамічні асинхронні частини слід розміщувати в Suspense.
Кешування потрібно застосовувати лише до результатів, безпечних для повторного використання між запитами.