← [ BACK_TO_NOTES ]

Next.js Cache Components

Next.js 16 的 Cache Components 模型通过 cacheComponents 与 use cache 显式标记可复用的数据或 UI 结果,并允许同一页面组合静态、缓存与请求时动态区域。

#concept #growing #frame #react

[!info] related notes

Next.js Cache Components

一句话定义

Next.js 16 的 Cache Components 模型把“缓存”从隐式默认行为变成更明确的边界:开启 cacheComponents 后,数据获取默认可以保持请求时动态;只有需要复用的函数、组件、Page 或 Layout 才通过 "use cache" 显式进入缓存作用域。

fresh-by-default runtime work
        +
explicit "use cache" islands
        +
Suspense / Streaming dynamic holes

基本配置

// next.config.ts
import type { NextConfig } from "next"

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

然后在需要缓存的异步边界里:

export async function getPosts() {
  "use cache"

  return postRepository.list()
}

或者直接缓存组件结果:

export default async function Page() {
  "use cache"

  const posts = await getPosts()
  return <PostList posts={posts} />
}

可以缓存什么

"use cache" 可以用于不同粒度:

Data function
→ 缓存函数结果

Server Component
→ 缓存组件输出

Page / Layout
→ 缓存更大的 UI 子树

所以它不是“Fetch API 的一个缓存参数”,而是一个 server-side computation / rendering cache boundary。

Data-level 与 UI-level Cache

Data-level

async function getProducts() {
  "use cache"
  return productApi.list()
}

适合:

  • 多个组件共享同一昂贵数据结果
  • 不想把整个页面都绑定到同一缓存生命周期
  • 希望缓存边界靠近真正的数据读取

UI-level

async function ProductSection() {
  "use cache"
  const products = await getProducts()
  return <ProductGrid products={products} />
}

适合:

  • 整个 UI 子树都由同一组相对稳定数据决定
  • 想直接复用计算/渲染结果

同一个页面可以混合三种区域

Cache Components 的重要价值不是把页面简单分成“Static 或 Dynamic”,而是允许:

Page
├── Static shell
├── Cached content
└── Fresh runtime content
      ↓
    Suspense / Streaming

例如:

Blog Page
├── Header              static
├── Article List        cached
└── User Notification   request-time fresh

这使缓存边界可以贴近真实业务需求,而不是整个 Route 一刀切。

不缓存也是明确选择

如果一份数据要求:

每次请求都应该拿当前状态

例如:

  • 正在运行的 Agent Run 状态
  • 高频变化的 seq/events
  • 强 freshness 的用户权限
  • 实时 Consultation 状态

就不要为了“Next.js 有 Cache”而强行 "use cache"。

<Suspense fallback={<RunSkeleton />}>
  <CurrentRun />
</Suspense>

更合适的模型是:

fresh request-time read
+
Suspense / Streaming
+
SSE / WebSocket live updates

Cache Key 从哪里来

缓存必须有身份。对于:

async function getUser(id: string) {
  "use cache"
  return userApi.get(id)
}

id 等参数会参与缓存身份,因此:

getUser("123")
≠
getUser("456")

这和 TanStack Query 的 queryKey 在思想上类似:缓存不能是没有身份的匿名副本。

但不要把两者视为同一个机制,见 Next.js Cache 与 TanStack Query 的关系。

Runtime Data 的边界

诸如:

cookies()
headers()
searchParams

属于请求到来后才知道的 runtime data。一个稳定做法是:

先读取 runtime value
      ↓
作为明确参数传入 cached function
      ↓
参数进入 cache identity

例如:

async function ProfilePage() {
  const sessionId = (await cookies()).get("session")?.value
  return <CachedProfile sessionId={sessionId ?? ""} />
}

async function CachedProfile({ sessionId }: { sessionId: string }) {
  "use cache"
  return getProfile(sessionId)
}

真正涉及私人 runtime data 的场景还存在 "use cache: private" 等能力,但应先保证身份、授权和隐私边界正确,再谈性能。

缓存不是 Truth

"use cache" 只是允许复用服务器结果,不会让缓存成为新的 authoritative owner:

Owning Service / DB
        ↓ authoritative truth

Next.js Cache
        ↓ server-side projection

Browser Cache / Query Cache
        ↓ client projection

一旦业务事实变化,缓存需要通过时间或事件重新验证。见 Next.js cacheLife 与 Next.js Tag-based Revalidation。

BodySense 边界

对 BodySense:

Assessment Result
→ 可否缓存要谨慎评估
→ user / assessment identity 必须明确
→ Go Backend 仍然是业务 owner

Active Agent Run
→ 不适合长缓存
→ fresh snapshot + SSE

即使缓存发生在 Next.js Server 层,也不能绕过 数据所有权与服务边界。

常见误区

“用了 Cache Components,所有 Server Component 都自动长期缓存”

错。当前模型强调显式缓存边界;不需要复用的 runtime work 可以保持动态。

“use cache = HTTP Cache-Control”

错。它是 Next.js 的应用/渲染缓存机制,不应直接等同浏览器 HTTP Cache-Control。

“缓存得越多越快,因此越好”

错。缓存同时引入 stale、身份、失效、跨用户隔离、观测和多层一致性问题。

最短记忆

cacheComponents
→ 开启新的显式缓存模型

"use cache"
→ 这个函数 / 组件 / 页面结果可以复用

没有 "use cache"
→ 可以保持 request-time fresh

Cache
≠ Truth
创建于 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
关闭浮层 / 取消命令;输入框内清空文本