Теги кешу та revalidateTag (Next.js)
Коротко: Next.js може кешувати дані сервера під іменованим «тегом» (а не лише за часом) — цільовий виклик revalidateTag() потім робить недійсними ЛИШЕ кешовані дані з цим тегом, не зачіпаючи решту кешу.
Докладніше: Дозволяє поєднувати TTL (час життя, як страховку) і цільове негайне оновлення саме за тих дій, які справді змінюють вихідні дані. Починаючи з Next.js 16 revalidateTag() потребує другого аргументу (профілю кешу, наприклад "max") — він керує поведінкою stale-while-revalidate: тег позначається застарілим, але вже не обов’язково одразу й жорстко видаляється для всіх клієнтів, як у старих версіях Next.js без цього другого аргументу.
Наш контекст: В Emzett app/utils/homeContentCache.ts кешує публікації головної сторінки/вибрані огляди через unstable_cache, TTL 60 с + цільова інвалідація через revalidateTag(HOME_CONTENT_CACHE_TAG, "max") за кожної зміни публікації. Для списку товарів магазину навмисно НЕ використовується — актуальні залишки для обмежених релізів мають бути точними; кешоване «залишилося лише 3» могло б призвести до перепродажу.
Подробиці
Базовий шаблон
Функція обгортається в unstable_cache() і при цьому отримує один або кілька тегів:
const getHomeContent = unstable_cache(
async () => db.query.posts.findMany({ where: eq(posts.featured, true) }),
["home-content"],
{ tags: [HOME_CONTENT_CACHE_TAG], revalidate: 60 },
);Коли публікація змінюється, відповідальна серверна дія викликає revalidateTag(HOME_CONTENT_CACHE_TAG, "max") — запис кешу негайно оголошується недійсним незалежно від ще чинного 60-секундного TTL. Це поєднує переваги обох світів: TTL як страховка (якщо інвалідацію колись забудуть, кеш знову свіжий не пізніше ніж після TTL), цільова інвалідація для негайної узгодженості за відомих змін.
Чому б просто не взяти один загальний тег для всього?
Важливо за кількох незалежних областей кешу: використовуйте якомога конкретніші й різні теги (замість одного загального тега на все) — інакше невелика зміна в одному місці без потреби робить недійсними й зовсім інші, незмінені кеші, що підриває сенс кешування (менше навантаження на базу даних). Поширена домовленість — називати теги ієрархічно (наприклад, posts, posts:featured, posts:${postId}) і за зміни інвалідувати лише зачеплений рівень — одна змінена публікація не обов’язково має робити недійсним увесь список обраного, якщо сама вона не вибрана.
Відмінність від суто часового кешування
Суто TTL-кешування (revalidate: 60 без тегів) простіше, але має структурний недолік: між реальною зміною даних і наступним спливом TTL користувачі бачать застарілі дані — за 60-секундного TTL у гіршому разі майже цілу хвилину. Інвалідація за тегами практично зводить це вікно до нуля для ВІДОМИХ змін, а TTL і далі править за страховку для НЕВІДОМИХ або забутих шляхів зміни (наприклад, пряме втручання в базу даних поза звичайним шляхом коду).
Другий аргумент з Next.js 16
Другий аргумент revalidateTag(), обов’язковий з Next.js 16 (профіль кешу на кшталт "max"), керує тим, наскільки агресивна інвалідація. Попередні версії Next.js знали лише жорстке негайне видалення одразу для всіх клієнтів — нова, детальніша модель натомість допускає поведінку, подібну до stale-while-revalidate, за якої вже доставлені відповіді, що ще зберігаються в браузері/CDN, не обов’язково одразу відкидаються, а можуть оновлюватися у фоні. Це одна з пасток Next.js 16, де застарілі знання навчання можуть ввести в оману ШІ-помічників, бо revalidateTag() раніше приймав лише один аргумент.
Див. також: Next.js App Router, React.cache(), TTL