🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx @anthropic-ai/skills install vercel-labs/next-skills/next-cache-components

💡 提示:需要 Node.js 和 NPM

缓存组件 (Next.js 16+)

缓存组件启用部分预渲染 (PPR) – 在单个路由中混合静态、缓存和动态内容。

启用缓存组件

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

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

此配置取代了旧的 experimental.ppr 标志。


三种内容类型

启用缓存组件后,内容分为三类:

1. 静态(自动预渲染)

同步代码、导入、纯计算 – 在构建时预渲染:

export default function Page() {
  return (
    <header>
      <h1>我们的博客</h1>  {/* 静态 - 即时 */}
      <nav>...</nav>
    </header>
  )
}

2. 缓存 (use cache)

不需要每次请求都获取新数据的异步数据:

async function BlogPosts() {
  'use cache'
  cacheLife('hours')

  const posts = await db.posts.findMany()
  return <PostList posts={posts} />
}

3. 动态 (Suspense)

必须保持新鲜的运行时数据 – 用 Suspense 包裹:

import { Suspense } from 'react'

export default function Page() {
  return (
    <>
      <BlogPosts />  {/* 缓存 */}

      <Suspense fallback={<p>加载中...</p>}>
        <UserPreferences />  {/* 动态 - 流式传入 */}
      </Suspense>
    </>
  )
}

async function UserPreferences() {
  const theme = (await cookies()).get('theme')?.value
  return <p>主题: {theme}</p>
}

use cache 指令

文件级别

'use cache'

export default async function Page() {
  // 整个页面被缓存
  const data = await fetchData()
  return <div>{data}</div>
}

组件级别

export async function CachedComponent() {
  'use cache'
  const data = await fetchData()
  return <div>{data}</div>
}

函数级别

export async function getData() {
  'use cache'
  return db.query('SELECT * FROM posts')
}

缓存配置文件

内置配置文件

'use cache'                    // 默认: 5分钟过期,15分钟重新验证
'use cache: remote'           // 平台提供的缓存 (Redis, KV)
'use cache: private'          // 用于合规要求,允许使用运行时 API

cacheLife() – 自定义生命周期

import { cacheLife } from 'next/cache'

async function getData() {
  'use cache'
  cacheLife('hours')  // 内置配置文件
  return fetch('/api/data')
}

内置配置文件:'default''minutes''hours''days''weeks''max'

内联配置

async function getData() {
  'use cache'
  cacheLife({
    stale: 3600,      // 1 小时 - 在重新验证期间提供过期内容
    revalidate: 7200, // 2 小时 - 后台重新验证间隔
    expire: 86400,    // 1 天 - 硬过期
  })
  return fetch('/api/data')
}

缓存失效

cacheTag() – 标记缓存内容

import { cacheTag } from 'next/cache'

async function getProducts() {
  'use cache'
  cacheTag('products')
  return db.products.findMany()
}

async function getProduct(id: string) {
  'use cache'
  cacheTag('products', `product-${id}`)
  return db.products.findUnique({ where: { id } })
}

updateTag() – 立即失效

当需要在同一个请求内刷新缓存时使用:

'use server'

import { updateTag } from 'next/cache'

export async function updateProduct(id: string, data: FormData) {
  await db.products.update({ where: { id }, data })
  updateTag(`product-${id}`)  // 立即 - 同一请求会看到新数据
}

revalidateTag() – 后台重新验证

用于过期-同时-重新验证行为:

'use server'

import { revalidateTag } from 'next/cache'

export async function createPost(data: FormData) {
  await db.posts.create({ data })
  revalidateTag('posts')  // 后台 - 下一个请求会看到新数据
}

运行时数据约束

不能use cache 内部访问 cookies()headers()searchParams

解决方案:作为参数传递

// 错误 - 在 use cache 内部使用运行时 API
async function CachedProfile() {
  'use cache'
  const session = (await cookies()).get('session')?.value  // 错误!
  return <div>{session}</div>
}

// 正确 - 在外部提取,作为参数传递
async function ProfilePage() {
  const session = (await cookies()).get('session')?.value
  return <CachedProfile sessionId={session} />
}

async function CachedProfile({ sessionId }: { sessionId: string }) {
  'use cache'
  // sessionId 会自动成为缓存键的一部分
  const data = await fetchUserData(sessionId)
  return <div>{data.name}</div>
}

例外:use cache: private

当无法重构且出于合规要求时使用:

async function getData() {
  'use cache: private'
  const session = (await cookies()).get('session')?.value  // 允许
  return fetchData(session)
}

缓存键生成

缓存键基于以下内容自动生成:

  • 构建 ID – 部署时使所有缓存失效
  • 函数 ID – 函数位置的哈希值
  • 可序列化参数 – 属性会成为键的一部分
  • 闭包变量 – 包含外部作用域的值
async function Component({ userId }: { userId: string }) {
  const getData = async (filter: string) => {
    'use cache'
    // 缓存键 = userId (闭包) + filter (参数)
    return fetch(`/api/users/${userId}?filter=${filter}`)
  }
  return getData('active')
}

完整示例

import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'

export default function DashboardPage() {
  return (
    <>
      {/* 静态外壳 - 从 CDN 即时返回 */}
      <header><h1>仪表板</h1></header>
      <nav>...</nav>

      {/* 缓存 - 快速,每小时重新验证 */}
      <Stats />

      {/* 动态 - 流式传入新数据 */}
      <Suspense fallback={<NotificationsSkeleton />}>
        <Notifications />
      </Suspense>
    </>
  )
}

async function Stats() {
  'use cache'
  cacheLife('hours')
  cacheTag('dashboard-stats')

  const stats = await db.stats.aggregate()
  return <StatsDisplay stats={stats} />
}

async function Notifications() {
  const userId = (await cookies()).get('userId')?.value
  const notifications = await db.notifications.findMany({
    where: { userId, read: false }
  })
  return <NotificationList items={notifications} />
}

从旧版本迁移

旧配置 替代方案
experimental.ppr cacheComponents: true
dynamic = 'force-dynamic' 移除(默认行为)
dynamic = 'force-static' 'use cache' + cacheLife('max')
revalidate = N cacheLife({ revalidate: N })
unstable_cache() 'use cache' 指令

unstable_cache 迁移到 use cache

unstable_cache 在 Next.js 16 中已被 use cache 指令取代。当启用 cacheComponents 时,将 unstable_cache 调用转换为 use cache 函数:

之前 (unstable_cache):

import { unstable_cache } from 'next/cache'

const getCachedUser = unstable_cache(
  async (id) => getUser(id),
  ['my-app-user'],
  {
    tags: ['users'],
    revalidate: 60,
  }
)

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const user = await getCachedUser(id)
  return <div>{user.name}</div>
}

之后 (use cache):

import { cacheLife, cacheTag } from 'next/cache'

async function getCachedUser(id: string) {
  'use cache'
  cacheTag('users')
  cacheLife({ revalidate: 60 })
  return getUser(id)
}

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const user = await getCachedUser(id)
  return <div>{user.name}</div>
}

主要区别:

  • 无需手动缓存键use cache 根据函数参数和闭包自动生成键。unstable_cache 中的 keyParts 数组不再需要。
  • 标签 – 用函数内部的 cacheTag() 调用替换 options.tags
  • 重新验证 – 用 cacheLife({ revalidate: N }) 或内置配置文件(如 cacheLife('minutes'))替换 options.revalidate
  • 动态数据unstable_cache 不支持在回调内部使用 cookies()headers()。同样的限制适用于 use cache,但如有需要,可以使用 'use cache: private'

限制

  • 不支持边缘运行时 – 需要 Node.js
  • 不支持静态导出 – 需要服务器
  • 非确定性值 (Math.random()Date.now()) 在 use cache 内部仅在构建时执行一次

对于缓存外部的请求时随机性:

import { connection } from 'next/server'

async function DynamicContent() {
  await connection()  // 推迟到请求时
  const id = crypto.randomUUID()  // 每个请求不同
  return <div>{id}</div>
}

来源:

📄 原始文档

完整文档(英文):

https://skills.sh/vercel-labs/next-skills/next-cache-components

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。