← [ BACK_TO_NOTES ]

Next.js Tag-based Revalidation

Next.js 用 cacheTag 为缓存结果建立业务失效身份,并通过 revalidateTag、updateTag 或 revalidatePath 在数据变化后选择后台刷新、立即过期或路径级失效。

#concept #growing #frame #react

[!info] related notes

Next.js Tag-based Revalidation

一句话定义

Tag-based Revalidation 把“这份缓存代表什么业务数据”显式编码为 tag,然后在业务事实变化时按 tag 重新验证,而不是只依赖时间到期或猜哪些页面碰巧使用了这份数据。

cached result
   ↓
cacheTag("products")
   ↓
业务数据变化
   ↓
revalidateTag / updateTag

cacheTag() 只负责标记身份

import { cacheTag } from "next/cache"

export async function getProducts() {
  "use cache"
  cacheTag("products")

  return productRepository.list()
}

这里:

cacheTag("products")

不会立刻把缓存标记为 stale,也不会主动刷新。

它做的是:

给当前缓存结果增加一个可用于未来 invalidation 的业务标签。

真正发生失效的是后续的 revalidateTag() 或 updateTag()。

这是一个非常重要的语义区别。

为什么 Tag 比 Path 更接近业务身份

假设 Product Catalog 同时出现在:

/home
/products
/search
/category/health

如果围绕页面路径失效:

revalidatePath("/home")
revalidatePath("/products")
revalidatePath("/search")
revalidatePath("/category/health")

会把缓存一致性绑定到 UI 信息架构。

更稳的方式是:

cacheTag("products")

更新后:

revalidateTag("products", "max")

表达的是:

产品数据变了,所有依赖这类缓存数据的结果都需要重新验证。

因此:

Path
→ UI location identity

Tag
→ cached data invalidation identity

revalidateTag():允许短暂 stale

当前推荐用法:

revalidateTag("products", "max")

核心语义:

matching cache entries
       ↓
旧结果不再被长期信任
       ↓
仍可以先服务 stale result
       ↓
后台生成 fresh result
       ↓
更新 cache

也就是 stale-while-revalidate。

适合:

  • Blog / CMS 内容
  • Product Catalog
  • 公开文档
  • 更新后允许短暂延迟的聚合结果

例如 Digital Biome 文章:

文章更新
→ revalidateTag("articles", "max")
→ 用户可能短暂看到旧文章列表
→ 后台刷新

如果几十秒旧内容可接受,这是合理的性能/一致性取舍。

updateTag():立即过期

如果用户刚完成写入并要求下一次读取立刻看到新值:

updateTag("goals")

语义是:

matching cache
   ↓
immediately expired
   ↓
下一次读取必须拿 fresh result

典型场景:

User creates Goal
      ↓
DB / owning service write succeeds
      ↓
updateTag("goals")
      ↓
redirect /goals
      ↓
用户马上看到刚创建的 Goal

这满足:

Read-your-own-writes

即“我刚写进去的东西,我下一次读就应该看到”。

当前 Next.js 中,updateTag() 只能在 Server Actions 中使用。

revalidateTag vs updateTag

允许短暂看到旧数据?
│
├─ Yes
│   → revalidateTag(tag, "max")
│   → stale-while-revalidate
│
└─ No
    → 用户刚写入后必须看到 fresh
    → updateTag(tag)
维度revalidateTagupdateTag
核心语义后台重新验证立即 expire
能否短暂返回 stale可以不应继续依赖旧缓存
典型场景CMS、Catalog、公开内容read-your-own-writes
使用位置Server Action / Route HandlerServer Action

updateTag() 不是乐观更新

这是最容易和 TanStack Query 混淆的地方。

updateTag

典型时间线:

Mutation 已经成功
      ↓
Server truth 已经改变
      ↓
updateTag()
      ↓
旧 server cache 立即失效
      ↓
下一次 read 重新拿 fresh truth

它做的是:

confirmed write 之后的 server-cache invalidation。

Optimistic Update

乐观更新则是:

Mutation 还没确认成功
      ↓
UI 先假设会成功
      ↓
显示预测结果
      ↓
Server response
  ├─ success → reconcile
  └─ failure → rollback

因此:

updateTag
≠ optimistic update

如果需要真正的乐观 UI,那属于 React/TanStack Query 的客户端交互策略,而不是 updateTag 本身。

revalidatePath():路径级失效

revalidatePath("/goals")

用于:

我明确知道需要重新验证某个 Route,但没有更合适的业务 tag。

它的粒度通常比 tag 更粗。

revalidatePath("/dashboard")
→ 围绕 Route 失效

revalidateTag("goal:123", "max")
→ 围绕数据身份失效

在能清楚定义 tag 的情况下,优先使用 tag-based invalidation 通常更精确,也更不容易 over-invalidate。

Tag 粒度怎么设计

可以有:

products
product:123
user:456
user:456:goals
goal:789

原则和 TanStack Query query key 很像:

As broad as necessary, as narrow as justified.

太窄:

只失效 goal detail
但 goal list / dashboard summary 也受影响

会留下 stale projection。

太宽:

修改一个 Goal title
→ updateTag("everything")

会制造无意义的重新计算。

Mutation 后应由谁触发失效

理想链路:

User Mutation
      ↓
Owning Service / DB commit
      ↓
确定事实已经改变
      ↓
Next.js invalidation
      ↓
Next server projection 收敛到新 truth

关键是不应该:

先 invalidate
→ mutation 最后失败

把缓存失效误当成业务事务本身。

缓存只能响应 truth 的变化,不能替代 truth 的写入。

Event-driven Invalidation

对于 CMS / Catalog 这种长时间稳定数据,可以:

long cacheLife
+
业务变化事件
+
revalidateTag

例如:

CMS publish
→ webhook
→ Route Handler
→ revalidateTag("articles", "max")

这比:

每分钟定时重新生成

更贴近真实变化,也能减少无效计算。

BodySense 边界

Active Agent Run:

高频 status / seq / events
→ 不应依赖长 server cache + tag invalidation 来模拟实时性
→ fresh snapshot + SSE

Assessment / User 数据:

如果真的引入 server cache
→ identity 必须包含用户/资源边界
→ mutation 后 invalidation 必须与 Go owning service 的成功写入对齐

缓存策略不能绕过 数据所有权与服务边界。

最短记忆

cacheTag
= 给缓存一个业务失效身份

revalidateTag
= 可以先旧后新

updateTag
= 立即 expire,支持 read-your-own-writes

revalidatePath
= 按 Route 粗粒度失效

updateTag
≠ optimistic update
创建于 2026/9/15 更新于 2026/9/15
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本