缓存策略设计

给一个页面定缓存方案的决策流程:数据归属 → 新鲜度要求 → 变更触发方式,落到 cacheLife / cacheTag / revalidateTag / 默认动态

决策流程:三问收敛到一行配置

先定性,再定量;跳过第一问直接讨论 revalidate 多少秒,是缓存事故的主要来源

给一个页面定缓存方案,依次回答三问:

  ① 数据归属:内容对所有访客一样吗?
  │
  ├── 不一样(会话/个人数据)
  │     → 不进共享缓存:保持默认动态,cookies()/headers() 取会话
  │       或干脆下沉为客户端取数(SWR,见 /data/client-fetching)
  │
  └── 一样(公共内容)
        │
        ② 新鲜度要求:数据过期多久算事故?
        │
        ├── 一秒都不行(行情、库存、计数器)
        │     → 什么都不写:cacheComponents 下取数默认动态,每请求实时渲染;
        │       需整页等请求到达时用 connection() 显式声明
        │
        ├── 分钟~小时级可接受(榜单、资讯流)
        │     → "use cache" + cacheLife({ revalidate: <秒数> })
        │       (时间驱动再生,对齐旧 export const revalidate)
        │
        └── 几乎不变,但变了要立即生效(文档、商品详情)
              │
              ③ 变更触发方式:谁通知缓存失效?
              ├── 定时轮询就够 → "use cache" + 长窗口 cacheLife(如 86400)
              └── 内容系统/后台有 webhook → "use cache" + cacheTag,
                    webhook 里 revalidateTag / revalidatePath 按需失效,
                    平时永久静态(见 /rendering/ppr)

三个场景卡:照流程走一遍

同样的问题清单,不同的答案组合出完全不同的配置

营销页 / 落地页

  • ① 归属:所有人一样 ✓
  • ② 新鲜度:小时级可接受
  • ③ 触发:发布频率低,定时即可

"use cache" + cacheLife(3600)

长窗口缓存(旧模型的长 ISR):构建后整页静态进 CDN,过期后后台再生; 流量越大越划算,首字节成本趋近于零

仪表盘 / 监控页

  • ① 归属:按登录人不同 ✗
  • ② 新鲜度:秒级
  • ③ 触发:持续变化,无所谓失效

默认动态(必要处 connection())

首屏出壳与基础数据,高频指标下沉客户端轮询 (SWR refreshInterval);共享缓存在这里只制造脏数据

内容站 / 商品详情

  • ① 归属:所有人一样 ✓
  • ② 新鲜度:平时无所谓,改稿须立即生效
  • ③ 触发:CMS 有发布 webhook

"use cache" + cacheTag → revalidateTag("post-123")

平时永久静态(缓存不设时限);CMS webhook 调 Route Handler, 按 tag 精准失效 —— 既有静态的成本,又有按需的新鲜度

决策要点:什么时候别上缓存方案

缓存是默认项之外的优化,不是每个页面都配拥有策略

  • 拿不准就先动态:cacheComponents 下「不声明缓存」就是安全的默认值, 缓存策略要在想清楚「脏了谁受影响」之后再用 "use cache" 引入;先缓存后排查, 比先动态后优化痛苦得多
  • 低流量页面别精心调参:一天几十次访问的后台页, ISR/tag 体系带来的运维复杂度超过省下的渲染成本
  • 别让标签体系失控:revalidateTag 的威力来自 精准的标签设计;标签命名没有规范(谁打、打几个、什么粒度)时, 按需失效很快退化成 revalidatePath("/") 式的全站轰炸
  • 机制底座(四层缓存各自的失效方式与排查顺序)见四层缓存对照台;本页是它的「选型上游」