mcpField
N
next-cache-components

next-cache-components

github.com/kokonut-labs开发2.3k52 次安装2 天前更新
代码仓库

关于 next-cache-components

面向配置部分预渲染与请求数据缓存的开发者,介绍 Next.js 16+ Cache Components。内容涵盖静态、缓存和动态内容、缓存时长、失效机制与迁移。

主要功能

  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` 用法

安装

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

$npx skills add https://github.com/kokonut-labs/kokonutui --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