← [ BACK_TO_NOTES ]

Next.js cacheLife

Next.js Cache Components 用 cacheLife 描述缓存的 stale、revalidate、expire 三阶段生命周期,分别控制客户端直接复用、服务端后台刷新与强制重新生成。

#concept #growing #frame #react

[!info] related notes

Next.js cacheLife

一句话定义

cacheLife() 用于 Next.js Cache Components 的 "use cache" 作用域,描述缓存结果在时间轴上的三个不同阶段:

stale
→ 客户端多久可以直接复用已有结果

revalidate
→ 多久后下一次访问应触发服务端后台刷新

expire
→ 旧结果最长可以存活到什么时候,之后必须重新生成

它解决的不是“要不要缓存”,而是:已经决定缓存以后,这份 projection 可以多旧、什么时候后台更新、什么时候必须 fresh。

基本写法

import { cacheLife } from "next/cache"

export async function getPosts() {
  "use cache"
  cacheLife("hours")

  return postRepository.list()
}

也可以自定义:

cacheLife({
  stale: 300,
  revalidate: 3600,
  expire: 86400,
})

Next.js 还提供 seconds、minutes、hours、days、weeks、max 等 profile,目的是让代码表达业务 freshness,而不是到处散落魔法数字。

stale:客户端直接复用窗口

在当前 Cache Components 模型中,stale 主要描述 Client Router Cache freshness window:

已有 route/cache result
        ↓
还在 stale window 内
        ↓
客户端可以直接复用

因此:

stale
≠ HTTP Cache-Control max-age

不要把它直接理解为浏览器普通 HTTP 缓存头。

一个实用直觉:

stale 回答“客户端这段时间内要不要重新向服务器确认”。

revalidate:先旧后新的后台刷新

超过 revalidate 周期以后,下一次请求可以:

Request
   ↓
已有缓存
   ↓
先返回旧结果
   ↓
后台重新计算 / 读取
   ↓
更新缓存

这是一种 stale-while-revalidate 风格:

low latency
+
eventual freshness

所以 revalidate 很适合:

  • 公开文章
  • 产品目录
  • CMS 内容
  • 允许短暂 stale 的聚合数据

expire:旧结果的硬上限

expire 表示服务器缓存不能无限依赖旧结果。

当缓存超过允许的最大生命周期,需要:

Request
  ↓
old cache no longer acceptable
  ↓
重新生成 fresh result
  ↓
再返回

因此最短区别是:

revalidate
= 可以先旧后新

expire
= 必须 fresh 后再继续

三段时间轴

用概念时间轴理解:

0 ───────── stale ───────── revalidate ───────── expire
│
├─ 客户端可直接复用
│
├────────── 到 revalidate 后:可先旧、后台刷新
│
└────────────────────────── 到 expire 后:必须重新生成

不要只背数字,要记住三者解决的是三个不同问题。

cacheLife 与业务语义

Digital Biome 公开文章

更新很少
所有用户大体相同
几十秒甚至更长 stale 可以接受

适合较长 profile,例如:

cacheLife("days")
// 或按实际发布节奏选择更长 profile

如果内容系统能主动通知更新,甚至可以用长缓存 + tag revalidation,而不是频繁时间刷新。

Product Catalog

访问量大
更新频率中等
短暂 stale 可接受

可以使用中等 cacheLife,并把真正的产品更新事件交给 Tag-based Revalidation。

Agent Run

seq / status / events
高频变化
freshness 要求高

此时问题不是“选 seconds 还是 minutes 更好”,而可能是根本不应该进入长缓存:

fresh snapshot
+
SSE

Time-based Revalidation 的边界

时间策略的优点:

简单
不依赖业务事件
自动恢复 freshness

缺点:

即使数据没变化,也可能重新计算
数据刚变化,也可能要等时间窗口

因此,如果系统明确知道:

“数据刚刚变了”

通常更适合 on-demand invalidation,而不是只等 cacheLife 时间过去。

与 TanStack Query staleTime 的区别

两者都出现“stale”这个词,但不要一一对号入座。

TanStack Query:

staleTime
→ 一份浏览器 Query projection 多久被视为 fresh

Next.js cacheLife:

stale
→ Client Router 对缓存 route/result 的直接复用窗口

revalidate
→ Server Cache 的后台刷新节奏

expire
→ Server Cache 的硬上限

因此:

Next cacheLife.stale
≠ TanStack Query staleTime 的同一个缓存层

Next cacheLife.expire
≠ TanStack Query gcTime

详细关系见 Next.js Cache 与 TanStack Query 的关系。

缓存时间不是正确性的替代品

错误做法:

“用户数据应该新一点”
→ 随便设 10 秒

真正需要先问:

这份数据允许多旧?
用户刚写入后能否看到旧值?
有没有业务事件能主动 invalidate?
多旧会产生什么风险?

例如余额、权限、医疗结果和正在执行的 Agent 状态,对 stale 的容忍度完全不同。

最短记忆

cacheLife
= 缓存时间语义

stale
= 客户端直接复用窗口

revalidate
= 先旧后新,后台刷新

expire
= 旧值硬上限,必须重新生成
创建于 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
关闭浮层 / 取消命令;输入框内清空文本