SKILL.md
readonlyread-only
name
next-cache-components
description
Next.js 16 快取元件 - PPR、use cache 指令、cacheLife、cacheTag、updateTag
快取元件(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>我們的部落格</h1> {/* 靜態 - 即時 */}
<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 /> {/* 快取 */}
<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)
}
快取鍵產生
快取鍵根據以下內容自動產生:
- 建置 ID - 部署時使所有快取失效
- 函式 ID - 函式位置的雜湊值
- 可序列化引數 - props 成為鍵的一部分
- 閉包變數 - 包含外部作用域的值
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
unstable_cache 已在 Next.js 16 中被 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陣列。 - 標籤 - 用函式內部的
cacheTag()呼叫取代options.tags。 - 重新驗證 - 用
cacheLife({ revalidate: N })或內建設定檔(如cacheLife('minutes'))取代options.revalidate。 - 動態資料 -
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() // 延遲到請求時期
const id = crypto.randomUUID() // 每次請求不同
return <div>{id}</div>
}
來源:






