next-cache-components by vercel-labs/next-skills
npx skills add https://github.com/vercel-labs/next-skills --skill next-cache-components缓存组件支持部分预渲染 (PPR) - 在单个路由中混合静态、缓存和动态内容。
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
这取代了旧的 experimental.ppr 标志。
启用缓存组件后,内容分为三类:
同步代码、导入、纯计算 - 在构建时预渲染:
export default function Page() {
return (
<header>
<h1>我们的博客</h1> {/* 静态 - 即时显示 */}
<nav>...</nav>
</header>
)
}
use cache)不需要每次请求都重新获取的异步数据:
async function BlogPosts() {
'use cache'
cacheLife('hours')
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
必须保持最新的运行时数据 - 用 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)
}
缓存键基于以下内容自动生成:
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在 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'。Math.random(), Date.now()) 在 use cache 内部仅在构建时执行一次对于缓存外的请求时随机性:
import { connection } from 'next/server'
async function DynamicContent() {
await connection() // 延迟到请求时间
const id = crypto.randomUUID() // 每个请求不同
return <div>{id}</div>
}
来源:
每周安装量
12.7K
代码仓库
GitHub 星标
774
首次出现
2026年1月26日
安全审计
安装于
github-copilot9.5K
opencode9.5K
codex9.5K
cursor9.3K
gemini-cli9.0K
claude-code8.4K
Cache Components enable Partial Prerendering (PPR) - mix static, cached, and dynamic content in a single route.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
This replaces the old experimental.ppr flag.
With Cache Components enabled, content falls into three categories:
Synchronous code, imports, pure computations - prerendered at build time:
export default function Page() {
return (
<header>
<h1>Our Blog</h1> {/* Static - instant */}
<nav>...</nav>
</header>
)
}
use cache)Async data that doesn't need fresh fetches every request:
async function BlogPosts() {
'use cache'
cacheLife('hours')
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}
Runtime data that must be fresh - wrap in 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 Directive'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() - Custom Lifetimeimport { cacheLife } from 'next/cache'
async function getData() {
'use cache'
cacheLife('hours') // Built-in profile
return fetch('/api/data')
}
Built-in profiles: '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() - Tag Cached Contentimport { 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() - Immediate InvalidationUse when you need the cache refreshed within the same request:
'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() - Background RevalidationUse for stale-while-revalidate behavior:
'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
}
Cannot access cookies(), headers(), or searchParams inside use cache.
// 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: privateFor compliance requirements when you can't refactor:
async function getData() {
'use cache: private'
const session = (await cookies()).get('session')?.value // Allowed
return fetchData(session)
}
Cache keys are automatic based on:
Build ID - invalidates all caches on deploy
Function ID - hash of function location
Serializable arguments - props become part of key
Closure variables - outer scope values included
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} />
}
| Old Config | Replacement |
|---|---|
experimental.ppr | cacheComponents: true |
dynamic = 'force-dynamic' | Remove (default behavior) |
dynamic = 'force-static' | 'use cache' + cacheLife('max') |
revalidate = N | cacheLife({ revalidate: N }) |
unstable_cache to use cacheunstable_cache has been replaced by the use cache directive in Next.js 16. When cacheComponents is enabled, convert unstable_cache calls to use cache functions:
Before (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>
}
After (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>
}
Key differences:
use cache generates keys automatically from function arguments and closures. The keyParts array from unstable_cache is no longer needed.options.tags with cacheTag() calls inside the function.options.revalidate with cacheLife({ revalidate: N }) or a built-in profile like cacheLife('minutes').unstable_cache did not support or inside the callback. The same restriction applies to , but you can use if needed.Math.random(), Date.now()) execute once at build time inside use cacheFor request-time randomness outside 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>
}
Sources:
Weekly Installs
12.7K
Repository
GitHub Stars
774
First Seen
Jan 26, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot9.5K
opencode9.5K
codex9.5K
cursor9.3K
gemini-cli9.0K
claude-code8.4K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
unstable_cache() | 'use cache' directive |
cookies()headers()use cache'use cache: private'