next-cache-components
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCache Components (Next.js 16+)
缓存组件(Next.js 16+)
Cache Components enable Partial Prerendering (PPR) - mix static, cached, and dynamic content in a single route.
缓存组件支持部分预渲染(PPR)——在单个路由中混合静态、缓存和动态内容。
Enable Cache Components
启用缓存组件
ts
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfigThis replaces the old flag.
experimental.pprts
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig这将替代旧的 标志。
experimental.pprThree Content Types
三种内容类型
With Cache Components enabled, content falls into three categories:
启用缓存组件后,内容分为三类:
1. Static (Auto-Prerendered)
1. 静态(自动预渲染)
Synchronous code, imports, pure computations - prerendered at build time:
tsx
export default function Page() {
return (
<header>
<h1>Our Blog</h1> {/* Static - instant */}
<nav>...</nav>
</header>
)
}同步代码、导入、纯计算逻辑——在构建时预渲染:
tsx
export default function Page() {
return (
<header>
<h1>我们的博客</h1> {/* 静态内容——即时加载 */}
<nav>...</nav>
</header>
)
}2. Cached (use cache
)
use cache2. 缓存(use cache
)
use cacheAsync data that doesn't need fresh fetches every request:
tsx
async function BlogPosts() {
'use cache'
cacheLife('hours')
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}不需要每次请求都获取最新数据的异步内容:
tsx
async function BlogPosts() {
'use cache'
cacheLife('hours')
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}3. Dynamic (Suspense)
3. 动态(Suspense 包裹)
Runtime data that must be fresh - wrap in Suspense:
tsx
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>
}必须保持最新的运行时数据——用 Suspense 包裹:
tsx
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
Directive
use cacheuse cache
指令
use cacheFile Level
文件级别
tsx
'use cache'
export default async function Page() {
// Entire page is cached
const data = await fetchData()
return <div>{data}</div>
}tsx
'use cache'
export default async function Page() {
// 整个页面都被缓存
const data = await fetchData()
return <div>{data}</div>
}Component Level
组件级别
tsx
export async function CachedComponent() {
'use cache'
const data = await fetchData()
return <div>{data}</div>
}tsx
export async function CachedComponent() {
'use cache'
const data = await fetchData()
return <div>{data}</div>
}Function Level
函数级别
tsx
export async function getData() {
'use cache'
return db.query('SELECT * FROM posts')
}tsx
export async function getData() {
'use cache'
return db.query('SELECT * FROM posts')
}Cache Profiles
缓存配置文件
Built-in Profiles
内置配置文件
tsx
'use cache' // Default: 5m stale, 15m revalidatetsx
'use cache: remote' // Platform-provided cache (Redis, KV)tsx
'use cache: private' // For compliance, allows runtime APIstsx
'use cache' // 默认:5分钟过期,15分钟后台重新验证tsx
'use cache: remote' // 平台提供的缓存(Redis、KV)tsx
'use cache: private' // 用于合规场景,允许使用运行时APIcacheLife()
- Custom Lifetime
cacheLife()cacheLife()
- 自定义生命周期
cacheLife()tsx
import { 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'tsx
import { cacheLife } from 'next/cache'
async function getData() {
'use cache'
cacheLife('hours') // 使用内置配置文件
return fetch('/api/data')
}内置配置文件:、、、、、
'default''minutes''hours''days''weeks''max'Inline Configuration
内联配置
tsx
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')
}tsx
async function getData() {
'use cache'
cacheLife({
stale: 3600, // 1小时——返回过期内容同时后台重新验证
revalidate: 7200, // 2小时——后台重新验证间隔
expire: 86400, // 1天——强制过期时间
})
return fetch('/api/data')
}Cache Invalidation
缓存失效
cacheTag()
- Tag Cached Content
cacheTag()cacheTag()
- 为缓存内容打标签
cacheTag()tsx
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 } })
}tsx
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()
- Immediate Invalidation
updateTag()updateTag()
- 即时失效
updateTag()Use when you need the cache refreshed within the same request:
tsx
'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
}当你需要在同一个请求中刷新缓存时使用:
tsx
'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()
- Background Revalidation
revalidateTag()revalidateTag()
- 后台重新验证
revalidateTag()Use for stale-while-revalidate behavior:
tsx
'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
}用于实现“返回过期内容同时后台更新”的行为:
tsx
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost(data: FormData) {
await db.posts.create({ data })
revalidateTag('posts') // 后台执行——下一次请求将获取最新数据
}Runtime Data Constraint
运行时数据限制
Cannot access , , or inside .
cookies()headers()searchParamsuse cache在 内部无法访问 、 或 。
use cachecookies()headers()searchParamsSolution: Pass as Arguments
解决方案:作为参数传入
tsx
// 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>
}tsx
// 错误示例——在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>
}Exception: use cache: private
use cache: private例外情况:use cache: private
use cache: privateFor compliance requirements when you can't refactor:
tsx
async function getData() {
'use cache: private'
const session = (await cookies()).get('session')?.value // Allowed
return fetchData(session)
}当你无法重构代码,且有合规要求时使用:
tsx
async function getData() {
'use cache: private'
const session = (await cookies()).get('session')?.value // 允许使用
return fetchData(session)
}Cache Key Generation
缓存键生成
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
tsx
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')
}缓存键会自动基于以下内容生成:
- 构建ID——部署时使所有缓存失效
- 函数ID——函数位置的哈希值
- 可序列化参数——props会成为键的一部分
- 闭包变量——包含外部作用域的值
tsx
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')
}Complete Example
完整示例
tsx
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} />
}tsx
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} />
}Migration from Previous Versions
从旧版本迁移
| Old Config | Replacement |
|---|---|
| |
| Remove (default behavior) |
| |
| |
| |
| 旧配置 | 替代方案 |
|---|---|
| |
| 移除(默认行为) |
| |
| |
| |
Limitations
限制
- Edge runtime not supported - requires Node.js
- Static export not supported - needs server
- Non-deterministic values (,
Math.random()) execute once at build time insideDate.now()use cache
For request-time randomness outside cache:
tsx
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:
- 不支持Edge运行时——需要Node.js环境
- 不支持静态导出——需要服务器
- 非确定性值(、
Math.random())在Date.now()内部会在构建时执行一次use cache
如需在缓存外实现请求级别的随机性:
tsx
import { connection } from 'next/server'
async function DynamicContent() {
await connection() // 延迟到请求时执行
const id = crypto.randomUUID() // 每次请求都不同
return <div>{id}</div>
}资料来源: