Пошук уроків, статей та іншого контенту
Кешуватимете результати довільних асинхронних функцій і налаштовуватимете їхні ключі та час життя.
unstable_cacheunstable_cache кешує результат довільної асинхронної функції. На відміну від кешування fetch, він підходить для:
запитів до бази даних;
викликів SDK;
обчислень, які повертають Promise;
читання даних із зовнішніх сервісів.
Функція повертає нову функцію-обгортку:
const cachedFunction = unstable_cache(originalFunction);Під час першого виклику cachedFunction виконується оригінальна функція, а її результат зберігається в кеші. Наступні виклики з тим самим ключем можуть отримати вже збережений результат.
Імпортувати API потрібно з next/cache:
import { unstable_cache } from 'next/cache';Назва містить unstable, тому API ще може змінитися в майбутніх версіях Next.js.
unstable_cache(fetchData, keyParts?, options?)Параметри:
fetchData — асинхронна функція, результат якої потрібно кешувати;
keyParts — додаткові частини ключа кешу;
options — налаштування часу життя та тегів.
Приклад базового використання:
import { unstable_cache } from 'next/cache';
async function getSettings() {
// Наприклад, тут може бути запит до бази даних
return {
theme: 'dark',
language: 'uk',
};
}
const getCachedSettings = unstable_cache(getSettings);
const settings = await getCachedSettings();Next.js формує ключ кешу на основі:
коду кешованої функції;
значень keyParts;
аргументів, переданих під час виклику.
Тому аргументи функції автоматично впливають на ключ:
import { unstable_cache } from 'next/cache';
async function getProduct(id) {
// Запит до бази даних за id
return { id, name: `Product ${id}` };
}
const getCachedProduct = unstable_cache(
getProduct,
['product'],
{ revalidate: 60 }
);
const firstProduct = await getCachedProduct('product-1');
const secondProduct = await getCachedProduct('product-2');У цьому прикладі product-1 і product-2 матимуть різні записи кешу, оскільки значення id входить у ключ.
keyPartskeyParts потрібен, коли кешована функція використовує значення із зовнішньої області видимості, а не отримує його аргументом.
import { unstable_cache } from 'next/cache';
const API_VERSION = 'v2';
async function fetchProducts() {
return fetch(`https://example.com/${API_VERSION}/products`)
.then((response) => response.json());
}
const getCachedProducts = unstable_cache(
fetchProducts,
['products', API_VERSION],
{ revalidate: 300 }
);Якщо зміниться API_VERSION, зміниться і ключ кешу.
Для більшості випадків краще передавати значення аргументами:
import { unstable_cache } from 'next/cache';
async function fetchProducts(version) {
return fetch(`https://example.com/${version}/products`)
.then((response) => response.json());
}
const getCachedProducts = unstable_cache(
fetchProducts,
['products'],
{ revalidate: 300 }
);
const products = await getCachedProducts('v2');Так залежність функції від даних стає явною.
Час життя налаштовується через revalidate. Значення вказується в секундах:
const getCachedProducts = unstable_cache(
fetchProducts,
['products'],
{
revalidate: 300,
}
);У цьому прикладі результат може повторно використовуватися протягом 300 секунд.
Після завершення цього періоду Next.js може повторно виконати функцію та оновити кеш. Таке оновлення називають фоновою або відкладеною ревалідацією: користувач може отримати старе значення, поки Next.js отримує нове.
Якщо revalidate не вказано або встановлено false, кеш не має автоматичного часу протухання:
const getCachedConfiguration = unstable_cache(
loadConfiguration,
['configuration'],
{
revalidate: false,
}
);Такий варіант підходить лише для даних, які змінюються рідко або оновлюються явним скиданням кешу.
Для даних, які можуть змінюватися, краще явно вказувати розумний інтервал:
const getCachedRates = unstable_cache(
loadExchangeRates,
['exchange-rates'],
{
revalidate: 900,
}
);Теги дозволяють пов’язати кеш із певною категорією даних. Тег не бере участі безпосередньо в унікальному ключі, але використовується для інвалідації кешу.
const getCachedProducts = unstable_cache(
fetchProducts,
['products'],
{
revalidate: 3600,
tags: ['products'],
}
);Усі записи, створені цією кешованою функцією, отримають тег products.
Теги особливо корисні, коли дані потрібно оновити одразу після зміни, не чекаючи завершення revalidate.
Наприклад, після створення або редагування товару можна викликати revalidateTag у серверному коді:
import { revalidateTag } from 'next/cache';
export async function updateProduct(product) {
await saveProduct(product);
// Інвалідуємо кеш, пов’язаний із товарами
revalidateTag('products');
}Після інвалідації наступний запит до кешованої функції отримає свіжі дані.
Сигнатура
revalidateTagможе відрізнятися між версіями Next.js. Використовуйте варіант, передбачений версією Next.js у вашому проєкті.
Нижче наведено приклад маршруту, який кешує асинхронне читання товару.
Файл app/api/products/[id]/route.js:
import { unstable_cache } from 'next/cache';
let databaseReads = 0;
async function readProduct(id) {
databaseReads += 1;
// Імітуємо повільне читання з бази даних
await new Promise((resolve) => setTimeout(resolve, 500));
return {
id,
name: `Product ${id}`,
databaseReads,
loadedAt: new Date().toISOString(),
};
}
const getCachedProduct = unstable_cache(
readProduct,
['product'],
{
revalidate: 60,
tags: ['products'],
}
);
export async function GET(request, { params }) {
const { id } = await params;
const product = await getCachedProduct(id);
return Response.json(product);
}Після запуску застосунку запит:
/api/products/42передасть 42 у getCachedProduct.
Перший виклик виконає readProduct. Наступні виклики з тим самим id протягом періоду ревалідації отримають кешований результат.
Виклик:
/api/products/43матиме інший ключ, оскільки аргумент id відрізняється.
У реальному застосунку замість readProduct можна використовувати функцію доступу до бази даних:
import { unstable_cache } from 'next/cache';
import { db } from '@/lib/db';
const getCachedUser = unstable_cache(
async (userId) => {
return db.user.findUnique({
where: { id: userId },
});
},
['user'],
{
revalidate: 120,
tags: ['users'],
}
);Не покладайтеся на змінні із замикання, якщо вони не включені до ключа кешу.
Невдалий приклад:
import { unstable_cache } from 'next/cache';
const locale = 'uk';
const getCachedMessage = unstable_cache(
async () => {
return loadMessage(locale);
},
['message']
);Якщо locale зміниться, ключ залишиться тим самим. Кеш може повернути результат для попередньої локалі.
Краще передавати локаль аргументом:
import { unstable_cache } from 'next/cache';
const getCachedMessage = unstable_cache(
async (locale) => {
return loadMessage(locale);
},
['message']
);
const message = await getCachedMessage('uk');Або додати значення до keyParts:
import { unstable_cache } from 'next/cache';
function createCachedMessageLoader(locale) {
return unstable_cache(
async () => loadMessage(locale),
['message', locale],
{ revalidate: 300 }
);
}
const getUkrainianMessage = createCachedMessageLoader('uk');
const message = await getUkrainianMessage();Перший варіант зазвичай простіший і прозоріший.
Не використовуйте cookies, headers та інші динамічні API безпосередньо всередині функції, яку передаєте в unstable_cache.
Невдалий варіант:
import { cookies } from 'next/headers';
import { unstable_cache } from 'next/cache';
const getCachedUser = unstable_cache(async () => {
const cookieStore = await cookies();
const userId = cookieStore.get('user-id')?.value;
return loadUser(userId);
});Така функція залежить від поточного HTTP-запиту, але її результат кешується як звичайне значення. У результаті дані одного користувача можуть бути використані для іншого.
Отримайте динамічне значення до виклику кешованої функції та передайте його аргументом:
import { cookies } from 'next/headers';
import { unstable_cache } from 'next/cache';
const getCachedUser = unstable_cache(
async (userId) => loadUser(userId),
['user'],
{ revalidate: 60 }
);
export async function getCurrentUser() {
const cookieStore = await cookies();
const userId = cookieStore.get('user-id')?.value;
if (!userId) {
return null;
}
return getCachedUser(userId);
}Тепер userId входить у ключ кешу, а кешована функція не читає стан поточного запиту самостійно.
Результат кешованої функції може бути повернутий іншому запиту з тим самим ключем. Тому не кешуйте без додаткового ключа:
персональні дані користувача;
дані, залежні від cookie або заголовків;
результати, які залежать від прав доступу;
одноразові або випадкові значення.
Якщо результат залежить від користувача, ідентифікатор користувача має бути аргументом функції або частиною keyParts.
Не використовуйте випадкові значення, поточний час або об’єкти, які змінюються між викликами, якщо вони не є справжньою частиною даних:
// Погано: кожен виклик фактично створює новий ключ
const result = await getCachedData(Math.random());Ключ повинен однозначно описувати дані, які потрібно отримати.
Якщо функція залежить від локалі, ролі або ідентифікатора користувача, ці значення потрібно врахувати в ключі:
const getCachedDashboard = unstable_cache(
async (userId, role) => loadDashboard(userId, role),
['dashboard'],
{ revalidate: 30 }
);
const dashboard = await getCachedDashboard('user-1', 'admin');Аргументи userId і role допоможуть розділити записи кешу.
unstable_cache і час виконанняКешування працює на сервері. unstable_cache не призначений для використання в клієнтських компонентах із директивою 'use client'.
Викликайте кешовані функції:
у серверних компонентах;
у Route Handlers;
у Server Actions;
в іншому серверному коді Next.js.
У режимі розробки поведінка кешу може відрізнятися від production. Для перевірки часу життя та повторного використання результатів перевіряйте production-збірку.
Краще створити кешовану обгортку на рівні модуля:
const getCachedProduct = unstable_cache(
getProduct,
['product'],
{ revalidate: 60 }
);а не створювати її щоразу всередині обробника:
export async function GET() {
const getCachedProduct = unstable_cache(
getProduct,
['product'],
{ revalidate: 60 }
);
return Response.json(await getCachedProduct('1'));
}Обгортка на рівні модуля робить структуру кешування зрозумілішою.
keyParts визначає, який запис кешу використовується.
['product']tags об’єднує записи для подальшої інвалідації:
{ tags: ['products'] }Це різні механізми:
ключ відповідає на питання: «Який саме запис потрібно отримати?»;
тег відповідає на питання: «Які записи потрібно інвалідувати разом?».
Якщо функція приймає ідентифікатор, переконайтеся, що цей ідентифікатор передається у кешовану функцію:
const getCachedProduct = unstable_cache(
async (id) => getProduct(id),
['product'],
{ revalidate: 60 }
);
await getCachedProduct('product-1');
await getCachedProduct('product-2');Не ховайте id у змінній, яка не входить до ключа.
unstable_cache кешує результати довільних асинхронних функцій.
Кешована функція створюється викликом unstable_cache.
Аргументи функції автоматично впливають на ключ кешу.
keyParts потрібні для додаткових значень, зокрема змінних із замикання.
revalidate задає час життя кешу в секундах.
tags дають змогу інвалідувати пов’язані записи кешу.
Не використовуйте cookies, headers та інші дані поточного запиту всередині кешованої функції.
Значення, від яких залежить результат, потрібно передавати аргументами або додавати до ключа.
Кешуйте лише ті дані, які безпечно повторно використовувати для запитів з однаковим ключем.