mcpField
N
next-cache-components

next-cache-components

github.com/connorads开发1865 次安装今天更新
代码仓库

关于 next-cache-components

next-cache-components 是面向 Next.js 16+ 开发者的缓存与部分预渲染指南,可在单一路由中组合静态、缓存和动态内容。它涵盖缓存配置、失效机制及从旧缓存 API 迁移。

主要功能

  1. 01启用 Cache Components 与部分预渲染
  2. 02用 use cache 标记缓存内容
  3. 03设置缓存周期和缓存配置
  4. 04使用标签进行缓存失效
  5. 05说明旧 API 迁移及运行限制

适用场景

  1. 01在同一路由组合静态与动态内容
  2. 02为异步数据设置缓存周期
  3. 03更新数据后按标签失效缓存
  4. 04将 unstable_cache 迁移至 use cache

安装

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

$npx skills add https://github.com/connorads/dotfiles --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