快取元件
快取元件是 Next.js 中一種新的渲染和快取方法,它透過**部分預渲染 (PPR)** 提供對快取內容和時間的細粒度控制,同時確保出色的使用者體驗。
快取元件
在開發動態應用程式時,您必須平衡兩種主要方法
- **完全靜態頁面**載入速度快,但無法顯示個性化或即時資料
- **完全動態頁面**可以顯示最新資料,但需要為每個請求渲染所有內容,導致初始載入速度變慢
啟用快取元件後,Next.js 預設將所有路由視為**動態路由**。每個請求都使用最新可用資料進行渲染。然而,大多數頁面由靜態和動態部分組成,並非所有動態資料都需要在每個請求中從源解析。
快取元件允許您將資料甚至部分 UI 標記為可快取,這些內容將與頁面的靜態部分一起包含在預渲染過程中。
**在快取元件之前**,Next.js 嘗試自動靜態最佳化**整個**頁面,這在新增動態程式碼時可能會導致意外行為。
快取元件實現了**部分預渲染 (PPR)** 和 `use cache`,為您提供了兩全其美的解決方案

當用戶訪問路由時
- 伺服器傳送一個**靜態 shell**,其中包含快取內容,確保快速初始載入
- 包裹在 `Suspense` 邊界中的動態部分在 shell 中顯示回退 UI
- 只有動態部分會渲染以替換其回退,並在準備好時並行流式傳輸
- 您可以透過使用 `use cache` 快取資料,將原本動態的資料包含在初始 shell 中
**🎥 觀看:** PPR 的原理及工作方式 → YouTube(10 分鐘)。
工作原理
快取元件為您提供了三個關鍵工具來控制渲染
1. 執行時資料的 Suspense
某些資料僅在實際使用者發出請求的執行時可用。`cookies`、`headers` 和 `searchParams` 等 API 訪問請求特定資訊。將使用這些 API 的元件包裝在 `Suspense` 邊界中,以便頁面的其餘部分可以作為靜態 shell 進行預渲染。
執行時 API 包括
cookiesheaders- `searchParams` prop
- `params` prop - 除非您透過 `generateStaticParams` 提供至少一個示例值,否則這是執行時資料。提供後,這些特定的 param 值在預渲染路徑中被視為靜態,而其他值則保持執行時
2. 動態資料的 Suspense
動態資料,如 `fetch` 呼叫或資料庫查詢 (`db.query(...)`),可能會在請求之間發生變化,但與使用者無關。`connection` API 是元動態的——它表示等待使用者導航,即使沒有實際資料可返回。將使用這些內容的元件包裝在 `Suspense` 邊界中以啟用流式傳輸。
動態資料模式包括
- `fetch` 請求
- 資料庫查詢
connection
3. 使用 `use cache` 快取資料
將 `use cache` 新增到任何伺服器元件,使其被快取幷包含在預渲染的 shell 中。您不能在快取元件中使用執行時 API。您還可以將實用函式標記為 `use cache` 並從伺服器元件中呼叫它們。
export async function getProducts() {
'use cache'
const data = await db.query('SELECT * FROM products')
return data
}使用 Suspense 邊界
React Suspense 邊界允許您定義在包裝動態或執行時資料時使用的回退 UI。
邊界外部的內容(包括回退 UI)作為靜態 shell 進行預渲染,而邊界內部的內容在準備好後流式傳輸。
以下是如何將 `Suspense` 與快取元件一起使用
import { Suspense } from 'react'
export default function Page() {
return (
<>
<h1>This will be pre-rendered</h1>
<Suspense fallback={<Skeleton />}>
<DynamicContent />
</Suspense>
</>
)
}
async function DynamicContent() {
const res = await fetch('http://api.cms.com/posts')
const { posts } = await res.json()
return <div>{/* ... */}</div>
}在構建時,Next.js 預渲染靜態內容和 `fallback` UI,而動態內容則推遲到使用者請求路由時。
**須知**:將元件包裝在 `Suspense` 中並不會使其變為動態;您的 API 使用才是。`Suspense` 充當封裝動態內容並啟用流式傳輸的邊界。
缺少 Suspense 邊界
快取元件強制動態程式碼必須包裝在 `Suspense` 邊界中。如果您忘記了,您將看到 `<Suspense>` 外部訪問了未快取的資料 錯誤
在 `<Suspense>` 外部訪問了未快取的資料
這會延遲整個頁面的渲染,導致使用者體驗緩慢。Next.js 使用此錯誤來確保您的應用程式在每次導航時都能即時載入。
要解決此問題,您可以
**將元件包裝在 `<Suspense>`** 邊界中。這允許 Next.js 在內容準備好後立即將其流式傳輸給使用者,而不會阻塞應用程式的其餘部分。
或者
**將非同步 await 移動到快取元件("use cache")中**。這允許 Next.js 將元件作為 HTML 文件的一部分靜態預渲染,因此使用者可以即時看到它。
請注意,請求特定資訊(如 params、cookies 和 headers)在靜態預渲染期間不可用,因此必須將其包裝在 `<Suspense>` 中。
此錯誤有助於防止出現使用者無法立即獲得靜態 shell 的情況,取而代之的是遇到一個阻塞的執行時渲染,卻沒有任何內容可顯示。要解決此問題,請新增 `Suspense` 邊界或使用 `use cache` 來快取工作。
流式傳輸的工作原理
流式傳輸將路由分成多個塊,並在準備好時逐步將它們流式傳輸到客戶端。這允許使用者立即看到頁面的部分內容,而無需等到整個內容渲染完成。

透過部分預渲染,初始 UI 可以立即傳送到瀏覽器,同時動態部分進行渲染。這減少了 UI 時間,並可能根據預渲染的 UI 量減少總請求時間。

為了減少網路開銷,包括靜態 HTML 和流式傳輸動態部分的完整響應會透過**單個 HTTP 請求**傳送。這避免了額外的往返,並改善了初始載入和整體效能。
使用 `use cache`
雖然 `Suspense` 邊界管理動態內容,但 `use cache` 指令可用於快取不經常更改的資料或計算。
基本用法
新增 `use cache` 以快取頁面、元件或非同步函式,並使用 `cacheLife` 定義生命週期
import { cacheLife } from 'next/cache'
export default async function Page() {
'use cache'
cacheLife('hours')
// fetch or compute
return <div>...</div>
}注意事項
使用 `use cache` 時,請記住這些限制
引數必須可序列化
與伺服器操作類似,快取函式的引數必須可序列化。這意味著您可以傳遞原始型別、普通物件和陣列,但不能傳遞類例項、函式或其他複雜型別。
在不自省的情況下接受不可序列化的值
您可以接受不可序列化的值作為引數,只要您不自省它們。但是,您可以返回它們。這允許像快取元件接受伺服器或客戶端元件作為子元件的模式
import { ReactNode } from 'react'
export async function CachedWrapper({ children }: { children: ReactNode }) {
'use cache'
// Don't introspect children, just pass it through
return (
<div className="wrapper">
<header>Cached Header</header>
{children}
</div>
)
}避免傳遞動態輸入
您不得將動態或執行時資料傳遞給 `use cache` 函式,除非您避免自省它們。將 `cookies()`、`headers()` 或其他執行時 API 的值作為引數傳遞會導致錯誤,因為在預渲染時無法確定快取鍵。
標籤和重新驗證
使用 `cacheTag` 標記快取資料,並在變異後使用伺服器操作中的 `updateTag` 進行立即更新,或者在可接受延遲更新的情況下使用 `revalidateTag` 重新驗證。
使用 `updateTag`
當您需要立即在同一請求中使快取資料過期並重新整理時,請使用 `updateTag`
import { cacheTag, updateTag } from 'next/cache'
export async function getCart() {
'use cache'
cacheTag('cart')
// fetch data
}
export async function updateCart(itemId: string) {
'use server'
// write data using the itemId
// update the user cart
updateTag('cart')
}使用 `revalidateTag`
當您只想使正確標記的快取條目失效,並採用陳舊資料重新驗證策略時,請使用 `revalidateTag`。這非常適合可以容忍最終一致性的靜態內容。
import { cacheTag, revalidateTag } from 'next/cache'
export async function getPosts() {
'use cache'
cacheTag('posts')
// fetch data
}
export async function createPost(post: FormData) {
'use server'
// write data using the FormData
revalidateTag('posts', 'max')
}有關更詳細的解釋和用法示例,請參閱 `use cache` API 參考。
啟用快取元件
您可以透過在 Next 配置檔案中新增 `cacheComponents` 選項來啟用快取元件(包括 PPR)
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig對路由分段配置的影響
啟用快取元件後,一些路由分段配置選項不再需要或受支援。以下是更改內容以及如何遷移
`dynamic = "force-dynamic"`
**不需要。** 啟用快取元件後,所有頁面預設都是動態的,因此此配置是不必要的。
// Before - No longer needed
export const dynamic = 'force-dynamic'
export default function Page() {
return <div>...</div>
}// After - Just remove it, pages are dynamic by default
export default function Page() {
return <div>...</div>
}`dynamic = "force-static"`
**替換為 `use cache`。** 您必須將 `use cache` 新增到相關路由的每個佈局和頁面中。
注意:`force-static` 以前允許使用 `cookies()` 等執行時 API,但現在不再支援。如果您新增 `use cache` 並看到與執行時資料相關的錯誤,則必須刪除執行時 API 的使用。
// Before
export const dynamic = 'force-static'
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>...</div>
}// After - Use 'use cache' instead
export default async function Page() {
'use cache'
const data = await fetch('https://api.example.com/data')
return <div>...</div>
}`revalidate`
**替換為 `cacheLife`。** 使用 `cacheLife` 函式來定義快取持續時間,而不是路由分段配置。
// Before
export const revalidate = 3600 // 1 hour
export default async function Page() {
return <div>...</div>
}// After - Use cacheLife
import { cacheLife } from 'next/cache'
export default async function Page() {
'use cache'
cacheLife('hours')
return <div>...</div>
}`fetchCache`
**不需要。** 使用 `use cache`,快取範圍內的所有資料獲取都會自動快取,使 `fetchCache` 不再需要。
// Before
export const fetchCache = 'force-cache'// After - Use 'use cache' to control caching behavior
export default async function Page() {
'use cache'
// All fetches here are cached
return <div>...</div>
}快取元件之前與之後
瞭解快取元件如何改變您的思維模式
快取元件之前
- **預設靜態**:Next.js 嘗試為您預渲染和快取儘可能多的內容,除非您選擇退出
- **路由級別控制**:`dynamic`、`revalidate`、`fetchCache` 等開關控制整個頁面的快取
- **`fetch` 的限制**:單獨使用 `fetch` 是不完整的,因為它不涵蓋直接資料庫客戶端或其他伺服器端 IO。巢狀的 `fetch` 切換到動態(例如 ` { cache: 'no-store' }`)可能會無意中改變整個路由行為
使用快取元件
- **預設動態**:所有內容預設都是動態的。您可以透過在需要的地方新增 `use cache` 來決定快取哪些部分
- **細粒度控制**:檔案/元件/函式級別的 `use cache` 和 `cacheLife` 精確控制您需要的快取
- **流式傳輸保持不變**:使用 `<Suspense>` 或 `loading.(js|tsx)` 檔案在 shell 立即顯示的同時流式傳輸動態部分
- **超越 `fetch`**:使用 `use cache` 指令,快取可以應用於所有伺服器 IO(資料庫呼叫、API、計算),而不僅僅是 `fetch`。巢狀的 `fetch` 呼叫不會悄悄地翻轉整個路由,因為行為由顯式快取邊界和 `Suspense` 控制
示例
動態 API
當訪問 `cookies()` 等執行時 API 時,Next.js 只會預渲染此元件上方的回退 UI。
在此示例中,我們沒有定義回退,因此 Next.js 顯示一個錯誤,指示我們提供一個。`<User />` 元件需要包裝在 `Suspense` 中,因為它使用了 `cookies` API
import { cookies } from 'next/headers'
export async function User() {
const session = (await cookies()).get('session')?.value
return '...'
}現在我們的使用者元件周圍有一個 `Suspense` 邊界,我們可以使用骨架 UI 預渲染頁面,並在特定使用者發出請求時流式傳輸 `<User />` UI
import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'
export default function Page() {
return (
<section>
<h1>This will be pre-rendered</h1>
<Suspense fallback={<AvatarSkeleton />}>
<User />
</Suspense>
</section>
)
}傳遞動態 props
元件僅在訪問值時才選擇動態渲染。例如,如果您從 `<Page />` 元件讀取 `searchParams`,您可以將此值作為 prop 轉發給另一個元件
import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'
export default function Page({
searchParams,
}: {
searchParams: Promise<{ sort: string }>
}) {
return (
<section>
<h1>This will be pre-rendered</h1>
<Suspense fallback={<TableSkeleton />}>
<Table searchParams={searchParams.then((search) => search.sort)} />
</Suspense>
</section>
)
}在表格元件內部,訪問 `searchParams` 中的值將使元件動態化,而頁面的其餘部分將被預渲染。
export async function Table({ sortPromise }: { sortPromise: Promise<string> }) {
const sort = (await sortPromise) === 'true'
return '...'
}常見問題
這會取代部分預渲染 (PPR) 嗎?
不。快取元件**實現了** PPR 作為一項功能。舊的實驗性 PPR 標誌已被移除,但 PPR 將繼續存在。
PPR 提供了靜態 shell 和流式傳輸基礎設施;`use cache` 允許您在有益時將最佳化的動態輸出包含在該 shell 中。
我應該首先快取什麼?
您快取的內容應該取決於您希望 UI 載入狀態如何。如果資料不依賴於執行時資料,並且您接受在一段時間內為多個請求提供快取值,請使用帶有 `cacheLife` 的 `use cache` 來描述該行為。
對於具有更新機制的內容管理系統,請考慮使用具有更長快取持續時間的標籤,並依賴 `revalidateTag` 將靜態初始 UI 標記為已準備好重新驗證。此模式允許您提供快速的快取響應,同時在內容實際更改時更新內容,而不是搶先使快取過期。
如何快速更新快取內容?
使用 `cacheTag` 標記您的快取資料,然後觸發 `updateTag` 或 `revalidateTag`。
這有幫助嗎?


