Коротка відповідь. Кеш у Next.js перестає бути магією, коли ти міняєш порядок питань. Не «який API викликати?», а спочатку: чиї це дані, наскільки свіжими вони мають бути для цього екрана, і хто має право їх інвалідувати? Відповівши, ти майже автоматично обираєш між трьома інструментами 16.3: revalidateTag(tag, 'max') – «оновиться скоро, користувач не чекає», updateTag(tag) – «користувач щойно сам змінив дані й мусить це побачити», refresh() – «онови поточний екран клієнта без тегів». Усе інше – деталі.
Три шари, які постійно плутають
Коли кажуть «кеш Next.js», ідеться мінімум про три різні речі з різними власниками:
- Request memoization. Дедуплікація однакових запитів у межах одного рендера одного запиту. Живе мілісекунди, нічого не треба інвалідувати – це просто «не ходи в базу двічі за один рендер».
- Data cache. Серверний кеш результатів: функції та компоненти з
'use cache', позначені тегами черезcacheTag('posts'), абоfetch(url, { next: { tags: ['posts'] } }). Живе між запитами й користувачами. Саме ним керують revalidateTag/updateTag. - Client router cache. Те, що браузер користувача вже отримав і тримає для миттєвих переходів назад/вперед. Саме через нього «я ж зробив revalidate, а користувач досі бачить старе» – сервер оновився, клієнтський знімок ні.
Баг-репорти про кеш майже завжди зводяться до того, що інвалідували один шар, а дивились на інший.
Спершу freshness-вимога, потім API
Для кожного читання даних у проєкті корисно мати явну відповідь на одне питання: що станеться поганого, якщо користувач побачить ці дані застарілими на хвилину?
- «Нічого» (блог, каталог, документація) → кешуй агресивно, інвалідуй через
revalidateTag(tag, 'max'). Семантика stale-while-revalidate: виклик лише позначає дані протухлими, наступний запит отримує stale-версію миттєво, а свіжа тягнеться у фоні. Важлива деталь із документації: ревалідацію запускає запит, а не сам виклик revalidateTag – сторінки оновлюються в міру відвідування, не всі одразу. - «Користувач щойно сам це змінив і дивиться на результат» → це read-your-own-writes, і для нього існує
updateTag(tag): дані протухають негайно, наступний запит чекає на свіжі. Працює лише в Server Actions – і це не обмеження, а підказка: такий сценарій і є «користувач натиснув кнопку». - «Дані на екрані не тегуються, але екран треба оновити» →
refresh()із Server Action оновлює клієнтський роутер для поточного перегляду.
Зверни увагу на зміну в 16.3: стара одноаргументна форма revalidateTag(tag) деприкована. Вона поводилась як { expire: 0 } – блокуючий miss для наступного запиту – і саме нею люди ненавмисно робили собі повільні сторінки. Тепер вибір явний: 'max' для SWR, updateTag для RYOW, { expire: 0 } – лише коли ти поза Server Action (webhook у Route Handler) і дані мусять зникнути негайно.
Приклад: адмінка редагує статтю, публічна сторінка оновлюється
Повний цикл read-your-own-writes у сучасному вигляді:
// app/article/[slug]/page.tsx – читання з тегом
import { cacheTag } from 'next/cache';
async function getArticle(slug: string) {
'use cache';
cacheTag(`article-${slug}`, 'articles');
return db.article.findUnique({ where: { slug } });
}// app/actions.ts – мутація
'use server';
import { updateTag, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
export async function saveArticle(slug: string, formData: FormData) {
await db.article.update({ where: { slug }, data: parse(formData) });
// Редактор має побачити СВОЮ зміну негайно:
updateTag(`article-${slug}`);
// Список статей може оновитись «скоро», без блокування:
revalidateTag('articles', 'max');
redirect(`/article/${slug}`);
}Два різні виклики тут – не випадковість, а сама суть моделі: одна й та сама мутація має різні freshness-вимоги для різних читачів. Автор дивиться на свою статтю – йому updateTag. Випадковий відвідувач списку – йому досить SWR.
Типові баги
- Вічний stale. Дані закешовані через
'use cache', алеcacheTagзабули – і тепер жоден revalidate їх не дістає, бо в них немає імені. Симптом: «інвалідую тег, нічого не змінюється». Перевірка завжди одна: чи тег, який ти інвалідуєш, реально призначений цим даним. - Stale тільки у того, хто натиснув. Мутація через Route Handler +
revalidateTagбез подальшого оновлення клієнта: сервер уже свіжий, а користувач дивиться на client router cache. У Server Action цю проблему закривають updateTag/refresh у відповіді самої дії. - Cache stampede своїми руками.
{ expire: 0 }на гарячому тегу означає: наступний запит кожного користувача – блокуючий похід у джерело. На трафіку це та сама лавина, про яку я писав у статті про кешування й Redis, тільки тепер її організував фреймворк за твоєю ж командою.'max'існує саме щоб цього не було. - Один тег на все.
revalidateTag('data', 'max')після кожної мутації – це не кешування, це глобальний reset із зайвими перерендерами. Тег має відповідати власнику даних:article-${slug},user-${id}, списковий тег окремо.
Чекліст для code review кешування
- У кожного
'use cache'єcacheTag, і тег згаданий хоча б в одній мутації (інакше навіщо кешували?). - Для кожної мутації явно видно freshness-рішення: updateTag (RYOW), revalidateTag + 'max' (SWR) чи refresh – і це рішення можна пояснити словами користувача.
{ expire: 0 }зустрічається лише в Route Handlers із зовнішніх тригерів, і ніколи – на найгарячіших тегах без коментаря чому.- Теги іменовані за власником даних, не за сторінкою.
- Немає «ритуальних» подвійних інвалідацій того самого тега різними API «про всяк випадок».
Як ці рішення лягають в архітектуру навігації – дивись у свіжому розборі Instant Navigations у Next.js 16.3: кеш і навігація в 16.3 – це фактично одна система, яку варто проєктувати разом. Базовий контекст по рендерингу – у огляді Next.js для React-розробника, а загальні патерни кешів без фреймворків – у frontend system design розборі.
Практичне завдання
Візьми одну мутацію зі свого проєкту й пройди ланцюжок письмово: які дані вона змінює → якими тегами ці дані позначені (перевір, що позначені!) → хто дивиться на ці дані в момент мутації → яка в кожного з них freshness-вимога → який API це виражає. Якщо на будь-якому кроці відповіді немає – ти щойно знайшов місце, де кеш «працює випадково». Поправ і повтори для наступної мутації: після третьої модель сяде в голову назавжди.