跳到內容

Next.js 中的快取

Next.js 透過快取渲染工作和資料請求,提升你的應用程式效能並降低成本。本頁面深入探討 Next.js 的快取機制、可用於設定它們的 API,以及它們如何相互作用。

要知道的好資訊:本頁面幫助你了解 Next.js 在底層是如何運作的,但對於有效率地使用 Next.js 來說,並非必要的知識。Next.js 的大多數快取啟發法是由你的 API 使用方式決定的,並且具有預設值,可在零或最少設定下獲得最佳效能。如果你想直接跳到範例,請從這裡開始

概觀

以下是不同快取機制及其用途的高階概觀

機制內容位置用途持續時間
請求記憶化函式的回傳值伺服器在 React 元件樹狀結構中重複使用資料每次請求的生命週期
資料快取資料伺服器跨使用者請求和部署儲存資料持久性 (可以重新驗證)
完整路由快取HTML 和 RSC 酬載伺服器降低渲染成本並提升效能持久性 (可以重新驗證)
路由器快取RSC 酬載客戶端減少導航時的伺服器請求使用者會話或基於時間

預設情況下,Next.js 會盡可能快取以提升效能並降低成本。這表示路由會靜態渲染,且資料請求會被快取,除非你選擇退出。下圖顯示預設的快取行為:路由在建置時靜態渲染,以及靜態路由首次被訪問時。

Diagram showing the default caching behavior in Next.js for the four mechanisms, with HIT, MISS and SET at build time and when a route is first visited.

快取行為會根據路由是靜態渲染還是動態渲染、資料是否被快取或未快取,以及請求是初始訪問還後續導航的一部分而改變。根據你的使用案例,你可以設定個別路由和資料請求的快取行為。

請求記憶化

Next.js 擴展了 fetch API 以自動記憶化具有相同 URL 和選項的請求。這表示你可以在 React 元件樹狀結構中的多個位置呼叫 fetch 函式以獲取相同的資料,而只執行一次。

Deduplicated Fetch Requests

例如,如果你需要在路由中使用相同的資料(例如在版面配置、頁面和多個元件中),你不需要在樹狀結構的頂端獲取資料,並在元件之間轉發 props。相反地,你可以在需要資料的元件中獲取資料,而無需擔心為了相同的資料跨網路發出多個請求的效能影響。

app/example.tsx
async function getItem() {
  // The `fetch` function is automatically memoized and the result
  // is cached
  const res = await fetch('https://.../item/1')
  return res.json()
}
 
// This function is called twice, but only executed the first time
const item = await getItem() // cache MISS
 
// The second call could be anywhere in your route
const item = await getItem() // cache HIT

請求記憶化如何運作

Diagram showing how fetch memoization works during React rendering.
  • 在渲染路由時,第一次呼叫特定請求時,其結果不會在記憶體中,並且會是快取 MISS
  • 因此,將執行該函式,並從外部來源獲取資料,且結果將儲存在記憶體中。
  • 在同一個渲染傳遞中後續呼叫的請求函式將是快取 HIT,並且資料將從記憶體中回傳,而無需執行該函式。
  • 一旦路由被渲染且渲染傳遞完成,記憶體將被「重置」,並且所有請求記憶化條目都將被清除。

要知道的好資訊:

  • 請求記憶化是 React 的功能,而不是 Next.js 的功能。此處包含它是為了展示它如何與其他快取機制互動。
  • 記憶化僅適用於 fetch 請求中的 GET 方法。
  • 記憶化僅適用於 React 元件樹狀結構,這表示
    • 它適用於 generateMetadatagenerateStaticParams、版面配置、頁面和其他伺服器元件中的 fetch 請求。
    • 它不適用於路由處理器中的 fetch 請求,因為它們不是 React 元件樹狀結構的一部分。
  • 對於 fetch 不適用的情況(例如,某些資料庫客戶端、CMS 客戶端或 GraphQL 客戶端),你可以使用 React cache 函式來記憶化函式。

持續時間

快取持續伺服器請求的生命週期,直到 React 元件樹狀結構完成渲染。

重新驗證

由於記憶化不會在伺服器請求之間共享,並且僅在渲染期間適用,因此無需重新驗證它。

選擇退出

記憶化僅適用於 fetch 請求中的 GET 方法,其他方法(例如 POSTDELETE)則不進行記憶化。此預設行為是 React 的最佳化,我們不建議選擇退出。

若要管理個別請求,你可以使用 signal 屬性,來自 AbortController。然而,這不會讓請求退出記憶化,而是中止正在進行中的請求。

app/example.js
const { signal } = new AbortController()
fetch(url, { signal })

資料快取

Next.js 具有內建的資料快取,可持久儲存跨傳入伺服器請求部署的資料獲取結果。這是可能的,因為 Next.js 擴展了原生 fetch API,允許伺服器上的每個請求設定自己的持久性快取語意。

要知道的好資訊:在瀏覽器中,fetchcache 選項指示請求將如何與瀏覽器的 HTTP 快取互動;在 Next.js 中,cache 選項指示伺服器端請求將如何與伺服器的資料快取互動。

你可以使用 cachenext.revalidate 選項來設定快取行為。

資料快取如何運作

Diagram showing how cached and uncached fetch requests interact with the Data Cache. Cached requests are stored in the Data Cache, and memoized, uncached requests are fetched from the data source, not stored in the Data Cache, and memoized.
  • 第一次在渲染期間呼叫具有 'force-cache' 選項的 fetch 請求時,Next.js 會檢查資料快取中是否有快取回應。
  • 如果找到快取回應,它會立即回傳並記憶化
  • 如果未找到快取回應,則會向資料來源發出請求,結果會儲存在資料快取中,並進行記憶化。
  • 對於未快取的資料(例如,未定義 cache 選項或使用 { cache: 'no-store' }),結果始終從資料來源獲取,並進行記憶化。
  • 無論資料是否被快取,請求始終會被記憶化,以避免在 React 渲染傳遞期間為相同的資料發出重複請求。

資料快取和請求記憶化之間的差異

雖然這兩種快取機制都有助於透過重複使用快取資料來提升效能,但資料快取在傳入請求和部署之間是持久的,而記憶化僅持續請求的生命週期。

持續時間

資料快取在傳入請求和部署之間是持久的,除非你重新驗證或選擇退出。

重新驗證

快取資料可以透過兩種方式重新驗證:

  • 基於時間的重新驗證:在經過一定時間後且發出新請求時,重新驗證資料。這對於不常變更且新鮮度不那麼重要的資料很有用。
  • 隨需重新驗證:根據事件(例如表單提交)重新驗證資料。隨需重新驗證可以使用基於標籤或基於路徑的方法來一次重新驗證多組資料。當你想確保盡快顯示最新資料時(例如,當你的 headless CMS 中的內容更新時),這很有用。

基於時間的重新驗證

若要以定時間隔重新驗證資料,你可以使用 fetchnext.revalidate 選項來設定資源的快取生命週期(以秒為單位)。

// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })

或者,你可以使用路由區段設定選項來設定區段中所有 fetch 請求,或用於你無法使用 fetch 的情況。

基於時間的重新驗證如何運作

Diagram showing how time-based revalidation works, after the revalidation period, stale data is returned for the first request, then data is revalidated.
  • 第一次呼叫具有 revalidate 的 fetch 請求時,資料將從外部資料來源獲取,並儲存在資料快取中。
  • 在指定的時限內(例如 60 秒)呼叫的任何請求都將回傳快取資料。
  • 在時限過後,下一個請求仍將回傳快取(現在已過期)的資料。
    • Next.js 將在背景觸發資料的重新驗證。
    • 一旦成功獲取資料,Next.js 將使用新鮮資料更新資料快取。
    • 如果背景重新驗證失敗,則先前的資料將保持不變。

這類似於 stale-while-revalidate 行為。

隨需重新驗證

可以透過路徑 (revalidatePath) 或快取標籤 (revalidateTag) 隨需重新驗證資料。

隨需重新驗證如何運作

Diagram showing how on-demand revalidation works, the Data Cache is updated with fresh data after a revalidation request.
  • 第一次呼叫 fetch 請求時,資料將從外部資料來源獲取,並儲存在資料快取中。
  • 當觸發隨需重新驗證時,適當的快取條目將從快取中清除。
    • 這與基於時間的重新驗證不同,後者會將過期資料保留在快取中,直到獲取新鮮資料。
  • 下一次發出請求時,它將再次是快取 MISS,並且資料將從外部資料來源獲取,並儲存在資料快取中。

選擇退出

如果你想快取來自 fetch 的回應,你可以執行以下操作

let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })

完整路由快取

相關術語:

你可能會看到術語自動靜態最佳化靜態網站產生靜態渲染被互換使用,以指稱在建置時渲染和快取應用程式路由的過程。

Next.js 會在建置時自動渲染和快取路由。這是一種最佳化,可讓你提供快取的路由,而不是在伺服器上為每個請求進行渲染,從而加快頁面載入速度。

若要了解完整路由快取如何運作,有助於了解 React 如何處理渲染,以及 Next.js 如何快取結果

1. 伺服器上的 React 渲染

在伺服器上,Next.js 使用 React 的 API 來協調渲染。渲染工作被分成多個區塊:按個別路由區段和 Suspense 邊界。

每個區塊都以兩個步驟渲染

  1. React 將伺服器元件渲染為一種特殊的資料格式,針對串流進行最佳化,稱為 React 伺服器元件酬載
  2. Next.js 使用 React 伺服器元件酬載和客戶端元件 JavaScript 指令,在伺服器上渲染 HTML

這表示我們不必等待所有內容都渲染完畢,才能快取工作或發送回應。相反地,我們可以在工作完成時串流回應。

什麼是 React 伺服器元件酬載?

React 伺服器元件酬載是渲染的 React 伺服器元件樹狀結構的緊湊二進制表示形式。客戶端上的 React 使用它來更新瀏覽器的 DOM。React 伺服器元件酬載包含

  • 伺服器元件的渲染結果
  • 應渲染客戶端元件的位置的佔位符,以及對其 JavaScript 檔案的參考
  • 從伺服器元件傳遞到客戶端元件的任何 props

若要了解更多資訊,請參閱伺服器元件文件。

2. 伺服器上的 Next.js 快取(完整路由快取)

Default behavior of the Full Route Cache, showing how the React Server Component Payload and HTML are cached on the server for statically rendered routes.

Next.js 的預設行為是在伺服器上快取路由的渲染結果(React 伺服器元件酬載和 HTML)。這適用於建置時或重新驗證期間靜態渲染的路由。

3. 客戶端上的 React Hydration 和 Reconciliation

在請求時,在客戶端上

  1. HTML 用於立即顯示客戶端和伺服器元件的快速非互動式初始預覽。
  2. React 伺服器元件酬載用於協調客戶端和渲染的伺服器元件樹狀結構,並更新 DOM。
  3. JavaScript 指令用於 hydrate 客戶端元件,並使應用程式具有互動性。

4. 客戶端上的 Next.js 快取(路由器快取)

React 伺服器元件酬載儲存在客戶端路由器快取中 - 一個單獨的記憶體快取,按個別路由區段分割。此路由器快取用於透過儲存先前訪問過的路由和預先獲取未來路由來改善導航體驗。

5. 後續導航

在後續導航或預先獲取期間,Next.js 將檢查 React 伺服器元件酬載是否儲存在路由器快取中。如果是,它將跳過向伺服器發送新請求。

如果路由區段不在快取中,Next.js 將從伺服器獲取 React 伺服器元件酬載,並在客戶端上填充路由器快取。

靜態和動態渲染

路由在建置時是否被快取取決於它是靜態渲染還是動態渲染。靜態路由預設情況下會被快取,而動態路由則在請求時渲染,且不會被快取。

此圖表顯示靜態渲染和動態渲染路由之間的差異,以及快取和未快取的資料

How static and dynamic rendering affects the Full Route Cache. Static routes are cached at build time or after data revalidation, whereas dynamic routes are never cached

深入了解靜態和動態渲染

持續時間

預設情況下,完整路由快取是持久的。這表示渲染輸出會在使用者請求之間被快取。

失效

有兩種方法可以使完整路由快取失效

  • 重新驗證資料:重新驗證資料快取,將進而使路由器快取失效,方法是在伺服器上重新渲染元件並快取新的渲染輸出。
  • 重新部署:與跨部署持久存在的資料快取不同,完整路由快取會在新的部署時清除。

選擇退出

你可以選擇退出完整路由快取,換句話說,為每個傳入請求動態渲染元件,方法是

  • 使用動態 API:這將使路由退出完整路由快取,並在請求時動態渲染它。資料快取仍然可以使用。
  • 使用 dynamic = 'force-dynamic'revalidate = 0 路由區段設定選項:這將跳過完整路由快取和資料快取。這表示元件將在每次傳入伺服器的請求時渲染和獲取資料。路由器快取仍然適用,因為它是客戶端快取。
  • 選擇退出資料快取:如果路由具有未快取的 fetch 請求,這將使路由退出完整路由快取。特定 fetch 請求的資料將針對每個傳入請求獲取。未選擇退出快取的其他 fetch 請求仍將在資料快取中快取。這允許快取和未快取資料的混合。

用戶端路由器快取

Next.js 具有記憶體內用戶端路由器快取,用於儲存路由區段的 RSC 有效負載,並依版面配置、載入狀態和頁面分割。

當使用者在路由之間導航時,Next.js 會快取已造訪的路由區段,並預先擷取使用者可能導航到的路由。這實現了即時的回上一頁/下一頁導航、導航之間沒有完整頁面重新載入,並保留了 React 狀態和瀏覽器狀態。

使用路由器快取

  • 版面配置會在導航時快取和重複使用(部分渲染)。
  • 載入狀態會在導航時快取和重複使用,以實現即時導航
  • 頁面預設不會快取,但在瀏覽器回上一頁和下一頁導航期間會重複使用。您可以使用實驗性的 staleTimes 設定選項,為頁面區段啟用快取。

须知: 此快取專門應用於 Next.js 和伺服器元件,並且與瀏覽器的 bfcache 不同,儘管它們有相似的結果。

持續時間

快取儲存在瀏覽器的暫時記憶體中。有兩個因素決定路由器快取的持續時間

  • 工作階段:快取在導航之間持續存在。但是,它會在頁面重新整理時清除。
  • 自動失效期間:版面配置和載入狀態的快取會在特定時間後自動失效。持續時間取決於資源的預先擷取方式,以及資源是否為靜態產生
    • 預設預先擷取 (prefetch={null} 或未指定):動態頁面不快取,靜態頁面快取 5 分鐘。
    • 完整預先擷取 (prefetch={true}router.prefetch):靜態和動態頁面都快取 5 分鐘。

雖然頁面重新整理會清除所有快取的區段,但自動失效期間僅影響自預先擷取時起的個別區段。

须知:實驗性的 staleTimes 設定選項可用於調整上述自動失效時間。

失效

有兩種方法可以使路由器快取失效

  • 伺服器動作
  • 呼叫 router.refresh 將使路由器快取失效,並向伺服器發出新請求以取得當前路由。

選擇退出

從 Next.js 15 開始,預設情況下頁面區段會選擇退出。

须知: 您也可以透過將 <Link> 元件的 prefetch 屬性設定為 false 來選擇退出預先擷取

快取互動

在設定不同的快取機制時,務必了解它們如何相互作用

資料快取和完整路由快取

  • 重新驗證或選擇退出資料快取將會使完整路由快取失效,因為渲染輸出取決於資料。
  • 使完整路由快取失效或選擇退出不會影響資料快取。您可以動態渲染同時具有快取和未快取資料的路由。當您的大部分頁面使用快取資料,但只有少數元件依賴需要在請求時擷取的資料時,這非常有用。您可以動態渲染,而無需擔心重新擷取所有資料的效能影響。

資料快取和用戶端路由器快取

  • 若要立即使資料快取和路由器快取失效,您可以在伺服器動作中使用 revalidatePathrevalidateTag
  • 路由處理器中重新驗證資料快取不會立即使路由器快取失效,因為路由處理器未與特定路由綁定。這表示路由器快取將繼續提供先前的有效負載,直到硬重新整理或自動失效期間結束。

API

下表概述了不同的 Next.js API 如何影響快取

API路由器快取完整路由快取資料快取React 快取
<Link prefetch>快取
router.prefetch快取
router.refresh重新驗證
fetch快取快取
fetch options.cache快取或選擇退出
fetch options.next.revalidate重新驗證重新驗證
fetch options.next.tags快取快取
revalidateTag重新驗證 (伺服器動作)重新驗證重新驗證
revalidatePath重新驗證 (伺服器動作)重新驗證重新驗證
const revalidate重新驗證或選擇退出重新驗證或選擇退出
const dynamic快取或選擇退出快取或選擇退出
cookies重新驗證 (伺服器動作)選擇退出
headers, searchParams選擇退出
generateStaticParams快取
React.cache快取
unstable_cache快取

預設情況下,<Link> 元件會自動從完整路由快取預先擷取路由,並將 React 伺服器元件有效負載新增至路由器快取。

若要停用預先擷取,您可以將 prefetch 屬性設定為 false。但這不會永久略過快取,當使用者造訪路由時,路由區段仍會在用戶端快取。

深入了解 <Link> 元件

router.prefetch

useRouter Hook 的 prefetch 選項可用於手動預先擷取路由。這會將 React 伺服器元件有效負載新增至路由器快取。

請參閱 useRouter Hook API 參考資料。

router.refresh

useRouter Hook 的 refresh 選項可用於手動重新整理路由。這會完全清除路由器快取,並向伺服器發出新請求以取得當前路由。refresh 不會影響資料或完整路由快取。

渲染結果將在用戶端協調,同時保留 React 狀態和瀏覽器狀態。

請參閱 useRouter Hook API 參考資料。

fetch

fetch 傳回的資料不會自動快取在資料快取中。

fetch 的預設快取行為(例如,當未指定 cache 選項時)等於將 cache 選項設定為 no-store

let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })

請參閱 fetch API 參考資料以取得更多選項。

fetch options.cache

您可以透過將 cache 選項設定為 force-cache,選擇將個別 fetch 加入快取。

// Opt into caching
fetch(`https://...`, { cache: 'force-cache' })

請參閱 fetch API 參考資料以取得更多選項。

fetch options.next.revalidate

您可以使用 fetchnext.revalidate 選項來設定個別 fetch 請求的重新驗證期間(以秒為單位)。這將重新驗證資料快取,進而重新驗證完整路由快取。將擷取新的資料,並且元件將在伺服器上重新渲染。

// Revalidate at most after 1 hour
fetch(`https://...`, { next: { revalidate: 3600 } })

請參閱 fetch API 參考資料以取得更多選項。

fetch options.next.tagsrevalidateTag

Next.js 具有快取標記系統,用於細緻的資料快取和重新驗證。

  1. 當使用 fetchunstable_cache 時,您可以選擇使用一個或多個標籤標記快取條目。
  2. 然後,您可以呼叫 revalidateTag 以清除與該標籤關聯的快取條目。

例如,您可以在擷取資料時設定標籤

// Cache data with a tag
fetch(`https://...`, { next: { tags: ['a', 'b', 'c'] } })

然後,使用標籤呼叫 revalidateTag 以清除快取條目

// Revalidate entries with a specific tag
revalidateTag('a')

您可以在兩個地方使用 revalidateTag,具體取決於您想要達成的目標

  1. 路由處理器 - 回應第三方事件(例如 Webhook)以重新驗證資料。由於路由處理器未與特定路由綁定,因此這不會立即使路由器快取失效。
  2. 伺服器動作 - 在使用者動作(例如表單提交)後重新驗證資料。這將使關聯路由的路由器快取失效。

revalidatePath

revalidatePath 可讓您在單一操作中手動重新驗證資料重新渲染特定路徑下的路由區段。呼叫 revalidatePath 方法會重新驗證資料快取,進而使完整路由快取失效。

revalidatePath('/')

您可以在兩個地方使用 revalidatePath,具體取決於您想要達成的目標

  1. 路由處理器 - 回應第三方事件(例如 Webhook)以重新驗證資料。
  2. 伺服器動作 - 在使用者互動(例如表單提交、按一下按鈕)後重新驗證資料。

請參閱 revalidatePath API 參考資料以取得更多資訊。

revalidatePath vs. router.refresh

呼叫 router.refresh 將清除路由器快取,並在伺服器上重新渲染路由區段,而不會使資料快取或完整路由快取失效。

差異在於 revalidatePath 會清除資料快取和完整路由快取,而 router.refresh() 不會變更資料快取和完整路由快取,因為它是用戶端 API。

動態 API

動態 API(如 cookiesheaders)以及 Pages 中的 searchParams 屬性取決於執行階段傳入的請求資訊。使用它們會使路由選擇退出完整路由快取,換句話說,路由將動態渲染。

cookies

在伺服器動作中使用 cookies.setcookies.delete 會使路由器快取失效,以防止使用 Cookie 的路由過時(例如,反映身分驗證變更)。

請參閱 cookies API 參考資料。

區段設定選項

路由區段設定選項可用於覆寫路由區段預設值,或者當您無法使用 fetch API 時(例如資料庫用戶端或第三方函式庫)。

以下路由區段設定選項將選擇退出完整路由快取

  • const dynamic = 'force-dynamic'

此設定選項將使所有擷取選擇退出資料快取(即 no-store

  • const fetchCache = 'default-no-store'

請參閱 fetchCache 以查看更多進階選項。

請參閱 路由區段設定 文件以取得更多選項。

generateStaticParams

對於動態區段(例如 app/blog/[slug]/page.js),generateStaticParams 提供的路徑會在建置時快取在完整路由快取中。在請求時,Next.js 也會在第一次造訪時快取建置時未知的路徑。

若要在建置時靜態渲染所有路徑,請將完整路徑清單提供給 generateStaticParams

app/blog/[slug]/page.js
export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json())
 
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

若要在建置時靜態渲染部分路徑,並在第一次在執行階段造訪時渲染其餘路徑,請傳回部分路徑清單

app/blog/[slug]/page.js
export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json())
 
  // Render the first 10 posts at build time
  return posts.slice(0, 10).map((post) => ({
    slug: post.slug,
  }))
}

若要在第一次造訪時靜態渲染所有路徑,請傳回空陣列(建置時不會渲染任何路徑)或使用 export const dynamic = 'force-static'

app/blog/[slug]/page.js
export async function generateStaticParams() {
  return []
}

须知: 您必須從 generateStaticParams 傳回陣列,即使它是空的也一樣。否則,路由將動態渲染。

app/changelog/[slug]/page.js
export const dynamic = 'force-static'

若要停用請求時的快取,請在路由區段中新增 export const dynamicParams = false 選項。當使用此設定選項時,只會提供 generateStaticParams 提供的路徑,其他路由將會 404 或符合(在全部捕捉路由的情況下)。

React cache 函式

React cache 函式可讓您記憶化函式的傳回值,讓您多次呼叫相同的函式,但只執行一次。

由於 fetch 請求會自動記憶化,因此您不需要將其包裝在 React cache 中。但是,您可以針對 fetch API 不適用的用例使用 cache 手動記憶化資料請求。例如,某些資料庫用戶端、CMS 用戶端或 GraphQL 用戶端。

utils/get-item.ts
import { cache } from 'react'
import db from '@/lib/db'
 
export const getItem = cache(async (id: string) => {
  const item = await db.item.findUnique({ id })
  return item
})