cache-components
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePROACTIVE ACTIVATION: Use this skill automatically when working in Next.js projects that have in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations.
cacheComponents: trueDETECTION: At the start of a session in a Next.js project, check for in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions.
cacheComponents: true主动激活:当在next.config.ts/next.config.js中配置了的Next.js项目中工作时,自动启用此技能。检测到该配置后,主动将Cache Components模式和最佳实践应用于所有React Server Components实现。
cacheComponents: true检测机制:在Next.js项目会话开始时,检查next.config中是否存在。如果已启用,此技能的模式应指导所有组件编写、数据获取和缓存决策。
cacheComponents: trueNext.js Cache Components
Next.js Cache Components
Auto-activation: This skill activates automatically in projects within next.config.cacheComponents: true
自动激活:当项目的next.config中存在时,此技能自动激活。cacheComponents: true
Project Detection
项目检测
When starting work in a Next.js project, check if Cache Components are enabled:
bash
undefined在Next.js项目中开始工作时,检查是否已启用Cache Components:
bash
undefinedCheck next.config.ts or next.config.js for cacheComponents
检查next.config.ts或next.config.js中的cacheComponents配置
grep -r "cacheComponents" next.config.* 2>/dev/null
If `cacheComponents: true` is found, apply this skill's patterns proactively when:
- Writing React Server Components
- Implementing data fetching
- Creating Server Actions with mutations
- Optimizing page performance
- Reviewing existing component code
Cache Components enable **Partial Prerendering (PPR)** - mixing static HTML shells with dynamic streaming content for optimal performance. Cache Components also enable state preservation during navigation with React's `<Activity>` component, which can keep cached component trees mounted but hidden.grep -r "cacheComponents" next.config.* 2>/dev/null
如果检测到`cacheComponents: true`,在以下场景中主动应用此技能的模式:
- 编写React Server Components
- 实现数据获取逻辑
- 创建带突变操作的Server Actions
- 优化页面性能
- 评审现有组件代码
Cache Components 支持**Partial Prerendering (PPR)**——将静态HTML外壳与动态流内容相结合,以实现最优性能。Cache Components还通过React的`<Activity>`组件在导航期间保留状态,该组件可以让缓存的组件树保持挂载但隐藏状态。Philosophy: Code Over Configuration
设计理念:代码优先,配置次之
Cache Components represents a shift from segment configuration to compositional code:
| Before (Deprecated) | After (Cache Components) |
|---|---|
| |
| Use |
| All-or-nothing static/dynamic | Granular: static shell + cached + dynamic |
Key Principle: Components co-locate their caching, not just their data. Next.js provides build-time feedback to guide you toward optimal patterns.
Cache Components代表了从分段配置到组合式代码的转变:
| 之前(已废弃) | 之后(Cache Components) |
|---|---|
| 在'use cache'作用域内使用 |
| 使用'use cache'和Suspense边界 |
| 全静态或全动态的二选一模式 | 细粒度控制:静态外壳 + 缓存内容 + 动态内容 |
核心原则:组件将缓存逻辑与数据逻辑内聚在一起,而不仅仅是数据。Next.js会在构建时提供反馈,引导你采用最优模式。
Core Concept
核心概念
┌─────────────────────────────────────────────────────┐
│ Static Shell │
│ (Sent immediately to browser) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Header │ │ Cached │ │ Suspense │ │
│ │ (static) │ │ Content │ │ Fallback │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Dynamic │ │
│ │ (streams) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────┐
│ 静态外壳 │
│ (立即发送至浏览器) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 头部组件 │ │ 缓存内容 │ │ Suspense │ │
│ │ (静态) │ │ │ │ 回退组件 │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ 动态内容 │ │
│ │ (流式传输) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────┘Mental Model: The Caching Decision Tree
思维模型:缓存决策树
When writing a React Server Component, ask these questions in order:
┌─────────────────────────────────────────────────────────┐
│ Does this component fetch data or perform I/O? │
└─────────────────────┬───────────────────────────────────┘
│
┌──────────▼──────────┐
│ YES │ NO → Pure component, no action needed
└──────────┬──────────┘
│
┌─────────────────▼─────────────────┐
│ Does it depend on request context? │
│ (cookies, headers, searchParams) │
└─────────────────┬─────────────────┘
│
┌────────────┴────────────┐
│ │
┌────▼────┐ ┌─────▼─────┐
│ YES │ │ NO │
└────┬────┘ └─────┬─────┘
│ │
│ ┌─────▼─────────────────┐
│ │ Can this be cached? │
│ │ (same for all users?) │
│ └─────┬─────────────────┘
│ │
│ ┌──────────┴──────────┐
│ │ │
│ ┌────▼────┐ ┌─────▼─────┐
│ │ YES │ │ NO │
│ └────┬────┘ └─────┬─────┘
│ │ │
│ ▼ │
│ 'use cache' │
│ + cacheTag() │
│ + cacheLife() │
│ │
│ Can you extract runtime │
│ data as params? │
│ │ │
│ ┌────▼────┐ ┌─────▼─────┐ │
│ │ YES │ │ NO │ │
│ └────┬────┘ └─────┬─────┘ │
│ │ │ │
│ Pass as args 'use cache: │
│ to cached fn private' │
│ (last resort) │
│ │
└──────────────┬─────────────────────┘
│
▼
Wrap in <Suspense>
(dynamic streaming)Key insight: The directive is for data that's the same across users. User-specific data stays dynamic with Suspense. If you cannot extract runtime data as function parameters and compliance prevents cross-request sharing, use as a last resort.
'use cache''use cache: private'编写React Server Component时,按顺序思考以下问题:
┌─────────────────────────────────────────────────────────┐
│ 该组件是否需要获取数据或执行I/O操作? │
└─────────────────────┬───────────────────────────────────┘
│
┌──────────▼──────────┐
│ 是 │ 否 → 纯组件,无需额外操作
└──────────┬──────────┘
│
┌─────────────────▼─────────────────┐
│ 它是否依赖请求上下文? │
│ (cookies、headers、searchParams) │
└─────────────────┬─────────────────┘
│
┌────────────┴────────────┐
│ │
┌────▼────┐ ┌─────▼─────┐
│ 是 │ │ 否 │
└────┬────┘ └─────┬─────┘
│ │
│ ┌─────▼─────────────────┐
│ │ 该数据是否可缓存? │
│ │ (对所有用户都相同?) │
│ └─────┬─────────────────┘
│ │
│ ┌──────────┴──────────┐
│ │ │
│ ┌────▼────┐ ┌─────▼─────┐
│ │ 是 │ │ 否 │
│ └────┬────┘ └─────┬─────┘
│ │ │
│ ▼ │
│ 'use cache' │
│ + cacheTag() │
│ + cacheLife() │
│ │
│ 能否将运行时数据提取为参数? │
│ │ │
│ ┌────▼────┐ ┌─────▼─────┐ │
│ │ 是 │ │ 否 │ │
│ └────┬────┘ └─────┬─────┘ │
│ │ │ │
│ 作为参数传递给缓存函数 'use cache: │
│ private' │
│ (最后手段) │
│ │
└──────────────┬─────────────────────┘
│
▼
用<Suspense>包裹
(动态流式传输)关键见解:指令适用于所有用户共享相同数据的场景。用户特定数据通过Suspense保持动态。如果无法将运行时数据提取为函数参数,且合规要求禁止跨请求共享数据,则将作为最后手段。
'use cache''use cache: private'Quick Start
快速开始
Enable Cache Components
启用Cache Components
typescript
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfigtypescript
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfigBasic Usage
基础用法
tsx
// Cached component - output included in static shell
async function CachedPosts() {
'use cache'
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}
// Page with static + cached + dynamic content
export default async function BlogPage() {
return (
<>
<Header /> {/* Static */}
<CachedPosts /> {/* Cached */}
<Suspense fallback={<Skeleton />}>
<DynamicComments /> {/* Dynamic - streams */}
</Suspense>
</>
)
}tsx
// 缓存组件 - 输出内容包含在静态外壳中
async function CachedPosts() {
'use cache'
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}
// 包含静态+缓存+动态内容的页面
export default async function BlogPage() {
return (
<>
<Header /> {/* 静态 */}
<CachedPosts /> {/* 缓存 */}
<Suspense fallback={<Skeleton />}>
<DynamicComments /> {/* 动态 - 流式传输 */}
</Suspense>
</>
)
}Server Actions vs Data Fetching (Critical Rule)
Server Actions vs 数据获取(关键规则)
Server Actions are for MUTATIONS ONLY - never for data fetching:
| Purpose | Use | Example Functions |
|---|---|---|
| Data fetch | Server Component / | |
| Mutation | Server Action ( | |
Server Actions仅用于突变操作——绝不能用于数据获取:
| 用途 | 正确用法 | 示例函数 |
|---|---|---|
| 数据获取 | Server Component / | |
| 突变操作 | Server Action ( | |
❌ WRONG: Server Action for Data Fetching
❌ 错误用法:用Server Action进行数据获取
tsx
"use server"
export async function getProducts() {
return await db.products.findMany() // NO! This is not a mutation
}
"use server"
export async function getTheme() {
return (await cookies()).get("theme")?.value // NO! Just reading data
}tsx
"use server"
export async function getProducts() {
return await db.products.findMany() // 错误!这不是突变操作
}
"use server"
export async function getTheme() {
return (await cookies()).get("theme")?.value // 错误!这只是读取数据
}✅ CORRECT: Data Function + Server Component
✅ 正确用法:数据函数 + Server Component
tsx
// data/products.ts - Cached data function
export async function getProducts() {
"use cache"
cacheTag("products")
cacheLife("hours")
return await db.products.findMany()
}
// page.tsx - Server Component reads data directly
import { cookies } from "next/headers"
export default async function Page() {
const products = await getProducts()
const theme = (await cookies()).get("theme")?.value ?? "light"
return <ProductList products={products} theme={theme} />
}tsx
// data/products.ts - 缓存数据函数
export async function getProducts() {
"use cache"
cacheTag("products")
cacheLife("hours")
return await db.products.findMany()
}
// page.tsx - Server Component直接读取数据
import { cookies } from "next/headers"
export default async function Page() {
const products = await getProducts()
const theme = (await cookies()).get("theme")?.value ?? "light"
return <ProductList products={products} theme={theme} />
}✅ CORRECT: Server Action for Mutation
✅ 正确用法:用Server Action进行突变操作
tsx
"use server"
import { updateTag } from "next/cache"
export async function createProduct(formData: FormData) {
await db.products.create({ data: formData })
updateTag("products") // Invalidate cache after mutation
}tsx
"use server"
import { updateTag } from "next/cache"
export async function createProduct(formData: FormData) {
await db.products.create({ data: formData })
updateTag("products") // 突变后使缓存失效
}Core APIs
核心API
1. 'use cache'
Directive
'use cache'1. 'use cache'
指令
'use cache'Marks code as cacheable. Can be applied at three levels:
tsx
// File-level: All exports are cached
'use cache'
export async function getData() {
/* ... */
}
export async function Component() {
/* ... */
}
// Component-level
async function UserCard({ id }: { id: string }) {
'use cache'
const user = await fetchUser(id)
return <Card>{user.name}</Card>
}
// Function-level
async function fetchWithCache(url: string) {
'use cache'
return fetch(url).then((r) => r.json())
}Important: All cached functions must be .
async标记代码为可缓存。可在三个级别应用:
tsx
// 文件级别:所有导出内容均被缓存
'use cache'
export async function getData() {
/* ... */
}
export async function Component() {
/* ... */
}
// 组件级别
async function UserCard({ id }: { id: string }) {
'use cache'
const user = await fetchUser(id)
return <Card>{user.name}</Card>
}
// 函数级别
async function fetchWithCache(url: string) {
'use cache'
return fetch(url).then((r) => r.json())
}重要提示:所有缓存函数必须是函数。
async2. cacheLife()
- Control Cache Duration
cacheLife()2. cacheLife()
- 控制缓存时长
cacheLife()tsx
import { cacheLife } from 'next/cache'
async function Posts() {
'use cache'
cacheLife('hours') // Use a predefined profile
// Or custom configuration:
cacheLife({
stale: 60, // 1 min - client cache validity
revalidate: 3600, // 1 hr - start background refresh
expire: 86400, // 1 day - absolute expiration
})
return await db.posts.findMany()
}Predefined profiles: , , , , , ,
'default''seconds''minutes''hours''days''weeks''max'tsx
import { cacheLife } from 'next/cache'
async function Posts() {
'use cache'
cacheLife('hours') // 使用预定义配置文件
// 或自定义配置:
cacheLife({
stale: 60, // 1分钟 - 客户端缓存有效期
revalidate: 3600, // 1小时 - 启动后台刷新
expire: 86400, // 1天 - 绝对过期时间
})
return await db.posts.findMany()
}预定义配置文件:, , , , , ,
'default''seconds''minutes''hours''days''weeks''max'3. cacheTag()
- Tag for Invalidation
cacheTag()3. cacheTag()
- 用于失效的标签
cacheTag()tsx
import { cacheTag } from 'next/cache'
async function BlogPosts() {
'use cache'
cacheTag('posts')
cacheLife('days')
return await db.posts.findMany()
}
async function UserProfile({ userId }: { userId: string }) {
'use cache'
cacheTag('users', `user-${userId}`) // Multiple tags
return await db.users.findUnique({ where: { id: userId } })
}tsx
import { cacheTag } from 'next/cache'
async function BlogPosts() {
'use cache'
cacheTag('posts')
cacheLife('days')
return await db.posts.findMany()
}
async function UserProfile({ userId }: { userId: string }) {
'use cache'
cacheTag('users', `user-${userId}`) // 多个标签
return await db.users.findUnique({ where: { id: userId } })
}4. updateTag()
- Immediate Invalidation
updateTag()4. updateTag()
- 立即失效
updateTag()For read-your-own-writes semantics:
tsx
'use server'
import { updateTag } from 'next/cache'
export async function createPost(formData: FormData) {
await db.posts.create({ data: formData })
updateTag('posts') // Client immediately sees fresh data
}用于实现读己所写语义:
tsx
'use server'
import { updateTag } from 'next/cache'
export async function createPost(formData: FormData) {
await db.posts.create({ data: formData })
updateTag('posts') // 客户端立即看到最新数据
}5. revalidateTag()
- Background Revalidation
revalidateTag()5. revalidateTag()
- 后台重新验证
revalidateTag()For stale-while-revalidate pattern:
tsx
'use server'
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string, data: FormData) {
await db.posts.update({ where: { id }, data })
revalidateTag('posts', 'max') // Serve stale, refresh in background
}用于实现stale-while-revalidate模式:
tsx
'use server'
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string, data: FormData) {
await db.posts.update({ where: { id }, data })
revalidateTag('posts', 'max') // 提供过期数据,后台刷新
}When to Use Each Pattern
模式选择指南
| Content Type | API | Behavior |
|---|---|---|
| Static | No directive | Rendered at build time |
| Cached | | Included in static shell, revalidates |
| Dynamic | Inside | Streams at request time |
| 内容类型 | API | 行为 |
|---|---|---|
| 静态 | 无指令 | 构建时渲染 |
| 缓存 | | 包含在静态外壳中,可重新验证 |
| 动态 | 包裹在 | 请求时流式传输 |
Parameter Permutations & Subshells
参数排列与子外壳
Critical Concept: With Cache Components, Next.js renders ALL permutations of provided parameters to create reusable subshells.
tsx
// app/products/[category]/[slug]/page.tsx
export async function generateStaticParams() {
return [
{ category: 'jackets', slug: 'classic-bomber' },
{ category: 'jackets', slug: 'essential-windbreaker' },
{ category: 'accessories', slug: 'thermal-fleece-gloves' },
]
}Next.js renders these routes:
/products/jackets/classic-bomber ← Full params (complete page)
/products/jackets/essential-windbreaker ← Full params (complete page)
/products/accessories/thermal-fleece-gloves ← Full params (complete page)
/products/jackets/[slug] ← Partial params (category subshell)
/products/accessories/[slug] ← Partial params (category subshell)
/products/[category]/[slug] ← No params (fallback shell)Why this matters: The category subshell () can be reused for ANY jacket product, even ones not in . Users navigating to an unlisted jacket get the cached category shell immediately, with product details streaming in.
/products/jackets/[slug]generateStaticParams核心概念:通过Cache Components,Next.js会渲染所有参数排列组合,以创建可复用的子外壳。
tsx
// app/products/[category]/[slug]/page.tsx
export async function generateStaticParams() {
return [
{ category: 'jackets', slug: 'classic-bomber' },
{ category: 'jackets', slug: 'essential-windbreaker' },
{ category: 'accessories', slug: 'thermal-fleece-gloves' },
]
}Next.js会渲染以下路由:
/products/jackets/classic-bomber ← 完整参数(完整页面)
/products/jackets/essential-windbreaker ← 完整参数(完整页面)
/products/accessories/thermal-fleece-gloves ← 完整参数(完整页面)
/products/jackets/[slug] ← 部分参数(分类子外壳)
/products/accessories/[slug] ← 部分参数(分类子外壳)
/products/[category]/[slug] ← 无参数(回退外壳)重要性:分类子外壳()可复用至任何夹克产品,即使该产品不在列表中。用户访问未列出的夹克产品时,会立即获取缓存的分类外壳,产品详情则流式传输。
/products/jackets/[slug]generateStaticParamsgenerateStaticParams
Requirements
generateStaticParamsgenerateStaticParams
要求
generateStaticParamsWith Cache Components enabled:
- Must provide at least one parameter - Empty arrays now cause build errors (prevents silent production failures)
- Params prove static safety - Providing params lets Next.js verify no dynamic APIs are called
- Partial params create subshells - Each unique permutation generates a reusable shell
tsx
// ❌ ERROR with Cache Components
export function generateStaticParams() {
return [] // Build error: must provide at least one param
}
// ✅ CORRECT: Provide real params
export async function generateStaticParams() {
const products = await getPopularProducts()
return products.map(({ category, slug }) => ({ category, slug }))
}启用Cache Components后:
- 必须提供至少一个参数 - 空数组会导致构建错误(避免静默生产故障)
- 参数需证明静态安全性 - 提供参数可让Next.js验证未调用动态API
- 部分参数创建子外壳 - 每个唯一的参数排列组合都会生成可复用的外壳
tsx
// ❌ 启用Cache Components时会报错
export function generateStaticParams() {
return [] // 构建错误:必须提供至少一个参数
}
// ✅ 正确用法:提供真实参数
export async function generateStaticParams() {
const products = await getPopularProducts()
return products.map(({ category, slug }) => ({ category, slug }))
}Cache Key = Arguments
缓存键 = 参数
Arguments become part of the cache key:
tsx
// Different userId = different cache entry
async function UserData({ userId }: { userId: string }) {
'use cache'
cacheTag(`user-${userId}`)
return await fetchUser(userId)
}参数会成为缓存键的一部分:
tsx
// 不同的userId对应不同的缓存条目
async function UserData({ userId }: { userId: string }) {
'use cache'
cacheTag(`user-${userId}`)
return await fetchUser(userId)
}Build-Time Feedback
构建时反馈
Cache Components provides early feedback during development. These build errors guide you toward optimal patterns:
Cache Components在开发阶段提供早期反馈。这些构建错误会引导你采用最优模式:
Error: Dynamic data outside Suspense
错误:Suspense边界外的动态数据
Error: Accessing cookies/headers/searchParams outside a Suspense boundarySolution: Wrap dynamic components in :
<Suspense>tsx
<Suspense fallback={<Skeleton />}>
<ComponentThatUsesCookies />
</Suspense>Error: Accessing cookies/headers/searchParams outside a Suspense boundary解决方案:用包裹动态组件:
<Suspense>tsx
<Suspense fallback={<Skeleton />}>
<ComponentThatUsesCookies />
</Suspense>Error: Uncached data outside Suspense
错误:Suspense边界外的未缓存数据
Error: Accessing uncached data outside SuspenseSolution: Either cache the data or wrap in Suspense:
tsx
// Option 1: Cache it
async function ProductData({ id }: { id: string }) {
'use cache'
return await db.products.findUnique({ where: { id } })
}
// Option 2: Make it dynamic with Suspense
;<Suspense fallback={<Loading />}>
<DynamicProductData id={id} />
</Suspense>Error: Accessing uncached data outside Suspense解决方案:要么缓存数据,要么用Suspense包裹:
tsx
// 选项1:缓存数据
async function ProductData({ id }: { id: string }) {
'use cache'
return await db.products.findUnique({ where: { id } })
}
// 选项2:用Suspense实现动态
;<Suspense fallback={<Loading />}>
<DynamicProductData id={id} />
</Suspense>Error: Request data inside cache
错误:缓存内的请求数据
Error: Cannot access cookies/headers inside 'use cache'Solution: Extract runtime data outside cache boundary (see "Handling Runtime Data" above).
Error: Cannot access cookies/headers inside 'use cache'解决方案:将运行时数据提取到缓存边界外(见上文“处理运行时数据”)。
Additional Resources
额外资源
- For complete API reference, see REFERENCE.md
- For common patterns and recipes, see PATTERNS.md
- For debugging and troubleshooting, see TROUBLESHOOTING.md
- 完整API参考,请查看REFERENCE.md
- 常见模式与实践,请查看PATTERNS.md
- 调试与故障排除,请查看TROUBLESHOOTING.md
Code Generation Guidelines
代码生成指南
When generating Cache Component code:
- Always use - All cached functions must be async
async - Place first - Must be first statement in function body
'use cache' - Call early - Should follow
cacheLife()directive'use cache' - Tag meaningfully - Use semantic tags that match your invalidation needs
- Extract runtime data - Move /
cookies()outside cached scopeheaders() - Wrap dynamic content - Use for non-cached async components
<Suspense> - Use as last resort - Only when runtime data cannot be extracted as params AND compliance requires no cross-request sharing
'use cache: private'
生成Cache Component代码时:
- 始终使用- 所有缓存函数必须是异步的
async - 将放在首位 - 必须是函数体的第一条语句
'use cache' - 尽早调用- 应紧跟
cacheLife()指令'use cache' - 有意义地标记 - 使用与失效需求匹配的语义化标签
- 提取运行时数据 - 将/
cookies()移到缓存作用域外headers() - 包裹动态内容 - 用包裹非缓存异步组件
<Suspense> - 仅在最后手段时使用- 仅当无法将运行时数据提取为参数且合规要求禁止跨请求共享时使用
'use cache: private'
Proactive Application (When Cache Components Enabled)
主动应用(当Cache Components启用时)
When is detected in the project, automatically apply these patterns:
cacheComponents: true当在项目中检测到时,自动应用以下模式:
cacheComponents: trueWhen Writing Data Fetching Components
编写数据获取组件时
Ask yourself: "Can this data be cached?" If yes, add :
'use cache'tsx
// Before: Uncached fetch
async function ProductList() {
const products = await db.products.findMany()
return <Grid products={products} />
}
// After: With caching
async function ProductList() {
'use cache'
cacheTag('products')
cacheLife('hours')
const products = await db.products.findMany()
return <Grid products={products} />
}自问:“此数据是否可缓存?”如果是,添加:
'use cache'tsx
// 之前:未缓存的获取
async function ProductList() {
const products = await db.products.findMany()
return <Grid products={products} />
}
// 之后:添加缓存
async function ProductList() {
'use cache'
cacheTag('products')
cacheLife('hours')
const products = await db.products.findMany()
return <Grid products={products} />
}When Writing Server Actions
编写Server Actions时
Always invalidate relevant caches after mutations:
tsx
'use server'
import { updateTag } from 'next/cache'
export async function createProduct(data: FormData) {
await db.products.create({ data })
updateTag('products') // Don't forget!
}突变操作后始终使相关缓存失效:
tsx
'use server'
import { updateTag } from 'next/cache'
export async function createProduct(data: FormData) {
await db.products.create({ data })
updateTag('products') // 不要忘记!
}When Composing Pages
组合页面时
Structure with static shell + cached content + dynamic streaming:
tsx
export default async function Page() {
return (
<>
<StaticHeader /> {/* No cache needed */}
<CachedContent /> {/* 'use cache' */}
<Suspense fallback={<Skeleton />}>
<DynamicUserContent /> {/* Streams at runtime */}
</Suspense>
</>
)
}按静态外壳 + 缓存内容 + 动态流的结构组织:
tsx
export default async function Page() {
return (
<>
<StaticHeader /> {/* 无需缓存 */}
<CachedContent /> {/* 使用'use cache' */}
<Suspense fallback={<Skeleton />}>
<DynamicUserContent /> {/* 运行时流式传输 */}
</Suspense>
</>
)
}When Reviewing Code
评审代码时
Flag these issues in Cache Components projects:
- Data fetching without where caching would benefit
'use cache' - Missing calls (makes invalidation impossible)
cacheTag() - Missing (relies on defaults which may not be appropriate)
cacheLife() - Server Actions without /
updateTag()after mutationsrevalidateTag() - /
cookies()called insideheaders()scope'use cache' - Dynamic components without boundaries
<Suspense> - DEPRECATED: - replace with
export const revalidateincacheLife()'use cache' - DEPRECATED: - replace with Suspense + cache boundaries
export const dynamic - Empty return - must provide at least one param
generateStaticParams()
在Cache Components项目中标记以下问题:
- 可从缓存受益的数据获取逻辑未使用
'use cache' - 缺少调用(使缓存失效无法实现)
cacheTag() - 缺少(依赖默认配置,可能不合适)
cacheLife() - 突变操作后的Server Actions未调用/
updateTag()revalidateTag() - 在作用域内调用
'use cache'/cookies()headers() - 动态组件未用边界包裹
<Suspense> - 已废弃:- 替换为
export const revalidate作用域内的'use cache'cacheLife() - 已废弃:- 替换为Suspense + 缓存边界
export const dynamic - 返回空数组 - 必须提供至少一个参数
generateStaticParams()