mcpField
N
next-cache-components

next-cache-components

github.com/vercel-labs开发1.7k1.9k 次安装34 周前更新
代码仓库

关于 next-cache-components

面向 Next.js 16+ Cache Components 的开发指引,说明如何在单个路由中组合静态、缓存和动态内容。适合配置缓存、失效策略及从旧缓存 API 迁移的开发者。

主要功能

  1. 01组合静态、缓存与动态内容
  2. 02通过 use cache 声明缓存
  3. 03配置缓存生命周期与标签
  4. 04即时失效及后台重新验证
  5. 05从旧配置和 API 迁移
  6. 06说明运行时限制

适用场景

  1. 01为路由配置部分预渲染
  2. 02设置缓存数据的有效期
  3. 03在数据更新后失效相关缓存
  4. 04将 unstable_cache 迁移至 use cache

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/openreview --skill next-cache-components

安装后在 Claude Code 中输入/next-cache-components确认已加载。

详细介绍

缓存组件(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>Our Blog</h1>  {/* Static - instant */}
      <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 />  {/* Cached */}

      <Suspense fallback={<p>Loading...</p>}>
        <UserPreferences />  {/* Dynamic - streams in */}
      </Suspense>
    </>
  )
}

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

use cache 指令

文件级别
'use cache'

export default async function Page() {
  // Entire page is cached
  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'                    // Default: 5m stale, 15m revalidate
'use cache: remote'           // Platform-provided cache (Redis, KV)
'use cache: private'          // For compliance, allows runtime APIs
cacheLife() —— 自定义生命周期
import { cacheLife } from 'next/cache'

async function getData() {
  'use cache'
  cacheLife('hours')  // Built-in profile
  return fetch('/api/data')
}

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

内联配置
async function getData() {
  'use cache'
  cacheLife({
    stale: 3600,      // 1 hour - serve stale while revalidating
    revalidate: 7200, // 2 hours - background revalidation interval
    expire: 86400,    // 1 day - hard expiration
  })
  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}`)  // Immediate - same request sees fresh data
}
revalidateTag() —— 后台重新验证

用于过期后重新验证行为:

'use server'

import { revalidateTag } from 'next/cache'

export async function createPost(data: FormData) {
  await db.posts.create({ data })
  revalidateTag('posts')  // Background - next request sees fresh data
}

运行时数据限制

在 use cache 中不能访问 cookies()、headers() 或 searchParams。

解决方案:作为参数传入
// Wrong - runtime API inside use cache
async function CachedProfile() {
  'use cache'
  const session = (await cookies()).get('session')?.value  // Error!
  return <div>{session}</div>
}

// Correct - extract outside, pass as argument
async function ProfilePage() {
  const session = (await cookies()).get('session')?.value
  return <CachedProfile sessionId={session} />
}

async function CachedProfile({ sessionId }: { sessionId: string }) {
  'use cache'
  // sessionId becomes part of cache key automatically
  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  // Allowed
  return fetchData(session)
}

缓存键生成

缓存键会自动根据以下内容生成:

  • 构建 ID —— 部署时使所有缓存失效
  • 函数 ID —— 函数位置的哈希值
  • 可序列化参数 —— props 会成为键的一部分
  • 闭包变量 —— 包含外层作用域的值
async function Component({ userId }: { userId: string }) {
  const getData = async (filter: string) => {
    'use cache'
    // Cache key = userId (closure) + filter (argument)
    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 (
    <>
      {/* Static shell - instant from CDN */}
      <header><h1>Dashboard</h1></header>
      <nav>...</nav>

      {/* Cached - fast, revalidates hourly */}
      <Stats />

      {/* Dynamic - streams in with fresh data */}
      <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.pprcacheComponents: true
dynamic = 'force-dynamic'移除(默认行为)
dynamic = 'force-static''use cache' + cacheLife('max')
revalidate = NcacheLife({ revalidate: N })
unstable_cache()'use cache' 指令
将 unstable_cache 迁移到 use cache

在 Next.js 16 中,unstable_cache 已由 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 数组。
  • 标签 —— 将 options.tags 替换为函数内的 cacheTag() 调用。
  • 重新验证 —— 将 options.revalidate 替换为 cacheLife({ revalidate: N }) 或 cacheLife('minutes') 等内置配置文件。
  • 动态数据 —— unstable_cache 不支持在回调中使用 cookies() 或 headers()。use cache 也有相同限制,但必要时可使用 'use cache: private'。

限制

  • 不支持 Edge 运行时 —— 需要 Node.js
  • 不支持静态导出 —— 需要服务器
  • 非确定性值(Math.random()、Date.now())在 use cache 中会于构建时执行一次

如需在缓存外生成请求时随机值:

import { connection } from 'next/server'

async function DynamicContent() {
  await connection()  // Defer to request time
  const id = crypto.randomUUID()  // Different per request
  return <div>{id}</div>
}

来源:

评论

支持 Markdown