Теги кэша и 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