关于 next-cache-components
面向 Next.js 16+ Cache Components 的开发指引,说明如何在单个路由中组合静态、缓存和动态内容。适合配置缓存、失效策略及从旧缓存 API 迁移的开发者。
主要功能
- 01组合静态、缓存与动态内容
- 02通过 use cache 声明缓存
- 03配置缓存生命周期与标签
- 04即时失效及后台重新验证
- 05从旧配置和 API 迁移
- 06说明运行时限制
适用场景
- 01为路由配置部分预渲染
- 02设置缓存数据的有效期
- 03在数据更新后失效相关缓存
- 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.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
在 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