Розуміння директиви „use cache“ та процесу перевірки актуальності на основі тегів у Next.js 16
Дізнайтеся, як працює директива «use cache» у Next.js 16, її супутні функції перевірки даних, та як застосовувати кешування з урахуванням користувачів у багатокористувацьких додатаках.
Кешування в Next.js традиційно вважалося своєрідною „чорною скринькою“ — сукупністю налаштувань на рівні файлів, параметрів, які передаються до fetch, та стандартних налаштувань фреймворку, які достатньо змінювалися між версіями, тож навіть досвідчені розробники тримали документацію відкритою для запобігання проблемам. Директива "use cache", введена у Next.js 16 як частина моделі Cache Components, змінює ситуацію, дозволяючи явно визначати поведінку кешування на рівні компонента чи функції, замість того щоб неявно успадковувати її зі стандартних налаштувань, які потрібно пам’ятати.
Нижче наведено пояснення того, що насправді робить ця директива, та ситуації, коли її доцільно використовувати.
Що насправді робить директива
Розміщення "use cache" всередині функції чи компонента повідомляє Next.js про необхідність кешування того, що повертає ця функція. Концептуально це виконує функцію, схожу на "use client", але замість позначення меж клієнтської частини воно позначає межі кешування:
async function getDashboardStats(tenantId: string) {
"use cache";
const stats = await db.query.stats.findMany({ where: { tenantId } });
return stats;
}
Результат виконання функції кешується та ідентифікується за допомогою аргументів, які були передані, після чого використовується знову у наступних запитах, доки щось не скасує його актуальність. Це суттєва відмінність від старшого підходу, коли кешувалося окреме запитання fetch або весь сегмент маршруту: тепер можна кешувати на будь-якому рівні деталізації, який підходить для ваших даних, аж до окремої функції.
Три допоміжні функції
Окрім цього директиви, Cache Components пропонують невеликий набір API для цілеспрямованого керування кешованими даними, замість простого очікування закінчення таймера:
revalidateTag(tag)— очищує кожну запис у кеші, пов’язану з заданим тегом. Це корисно, коли одна зміна впливає на дані, від яких залежать кілька різних функцій у кеші.updateTag(tag)— вужча версія тієї ж ідеї, призначена для оновлення конкретних записів у кеші, пов’язаних з одним тегом, а не всього, що знаходиться під ним.refresh()— оновлює дані у кеші, які стосуються поточного запиту.
Ось принцип, який робить увесь систему ефективною: прикріпіть змістовну тег до кожної функції, що зберігається в кеші — щось на кшталт tenant-stats або invoice-list — і щоразу, коли відбувається зміна, яка впливає на ці дані, викличте revalidateTag з відповідним тегом, замість того щоб намагатися вибрати часовий проміжок закінчення терміну дії, який буде або занадто коротким (що суперечить меті кешування), або занадто довгим (що призведе до використання застарілих даних).
async function getInvoices(tenantId: string) {
"use cache";
cacheTag(`invoices-${tenantId}`);
return db.query.invoices.findMany({ where: { tenantId } });
}
// After creating an invoice:
async function createInvoice(data: InvoiceInput) {
await db.insert(invoices).values(data);
revalidateTag(`invoices-${data.tenantId}`);
}
Саме ця комбінація — позначення тегами під час читання та перевірка їх дійсності під час запису — і є суттю всієї системи у скороченому вигляді. Майже все інше, що ви будете робити з цією системою, є варіацією саме цього підходу.
Поширені моменти плутанини
Найпоширеніша помилка, в яку потрапляють команди, — це припущення, що "use cache" є простим замінником опції next: { revalidate } у функції fetch(). Насправді вони вирішують різні, хоча й пов’язані між собою, проблеми. Опція на рівні fetch() керує одним мережевим запитом. "use cache" кешує результат цілої функції чи компонента, який всередині себе може виконувати набагато більше завдань — звертатися до бази даних, виконувати певні обчислення чи навіть викликати саму функцію fetch. Якщо вам потрібно лише кешувати один зовнішній API-запит, використання кешування на рівні fetch() зазвичай є простішим варіантом. "use cache" стає корисним тоді, коли ви хочете кешувати цілий обчислений результат, а не лише окремий запит, який його генерує.
Другою поширеною помилкою є повне пропускання кроку позначення тегами, що згодом призводить до плутанини, коли мутація не вдається очистити збережені дані. Без прикріпленого тега єдиним механізмом анулювання є час, що підриває основну мету використання цієї моделі — адже ви берете на себе додаткову складність прямого кешування, не отримуючи при цьому прямого контролю над анулюванням.
Теги, що враховують користувачів, для багатокористувацьких додатків
Якщо ви працюєте над системою з кількома орендарями, є одна деталь, яку варто відразу зазначити: теги мають кодувати саме орендаря, а не лише тип даних, які зберігаються у кеші. Універсальний тег на кшталт invoices, який використовується всіма орендарями, означає, що скасування даних одного орендаря вплине на всіх — що може призвести або до проблем з коректністю (один орендар бачить застарілі дані через те, що зміни іншого орендаря спричинили спільну перевірку даних), або до проблем з продуктивністю (кеш очищується набагато частіше, ніж це необхідно). Формат на кшталт invoices-${tenantId}, як у прикладі вище, — це не справа стилю; саме це відрізняє ефективну стратегію скасування даних від неефективної.
Чому варто правильно налаштувати це з самого початку
Шар кешування з непомітною помилкою рідко виявляє проблеми у очевидній формі. Натомість це зазвичай проявляється у нечітких заявах підтримки на кшталт „Чому ця панель керування все ще відображає дані минулого тижня“ — це помилки, які дуже складно виявити, оскільки неправильний механізм скасування кешу часто знаходиться там, де ніхто не торкався протягом кількох місяців. Раннє впровадження послідовної стратегії позначення елементів, яка буде єдиною для всіх функцій з кешуванням у кодовій базі, є одним із тих виборів інфраструктури, які дешево вирішити на початковому етапі, але значно дорожче виправити пізніше.
Деякі набори для створення панелей керування будують свій шар даних саме на основі цих принципів. Наприклад, шаблон на кшталт шаблону панелі керування від Ovyqen для проектів Next.js та SaaS використовує підхід «додавання тега при читанні, перевірка достовірності при запису» у всьому процесі, вбудовуючи ідентифікатори користувачів у кожен тег з самого початку, а не додаючи їх пізніше у разі проблем із кешуванням між користувачами. Якщо ви розглядаєте різні шаблони панелей керування, а недостовірність кешу є тією частиною вашого існуючого додатку, якій ніхто не довіряє, це є вагомою причиною почати роботу з основи, яка вже правильно вирішує цю проблему.
Часто ставлені запитання
Чи слід кожен виклик fetch() мігрувати на "use cache"? Не обов’язково — ці два інструменти частково перетинаються, але не є взаємозамінними. Кешування на рівні fetch() все ще підходить для простих сценаріїв з одним запитом. Використовуйте "use cache", коли вам потрібно зберігати у кеші обчислений результат або вихідні дані всього компонента.
Чи достатньо зрілим є "use cache" для продакшн-панелей? Ставіться до нього так само, як до будь-якого відносно молодого механізму кешування — ретельно протестуйте шляхи анулювання даних у кеші, особливо для даних багатьох користувачів, перш ніж покладатися на нього для чогось, що спрямоване до клієнтів та де важлива актуальність інформації.
Що відбувається, якщо функція з кешу залишиться без позначки? Кешування все одно відбувається, але ви втрачаєте можливість навмисно скасувати його у відповідь на певну подію. Ви змушені покладатися виключно на термін дії, заснований на часі, що рідко є бажаною поведінкою.
Явне кешування вимагає більше зусиль на початковому етапі, ніж просте довіряння значенням за замовчуванням фреймворку. Але для даних панелі керування, де відображення застарілої інформації має реальні наслідки, ці додаткові зусилля майже завжди є правильним рішенням.
Пов’язана література
- Розуміння компонентів кешу та часткового попереднього завантаження в Next.js 16.3 — Пояснює, як функція миттєвого навігування в Next.js 16.3 використовує спільні шелли маршрутів та явні рішення щодо стрімінгу, щоб зробити додатки, які генеруються на сервері, миттєвими у використанні.