Next.js 中的快取機制

Next.js 透過快取渲染工作和資料請求,提升應用程式效能並降低成本。本頁深入探討 Next.js 的快取機制、可用的配置 API 以及它們之間的互動方式。

須知事項:本頁幫助您理解 Next.js 的底層運作,但並非使用 Next.js 的必備知識。Next.js 的快取策略大多由您的 API 使用方式決定,並預設提供最佳效能,無需或僅需極少配置。

概觀

以下是不同快取機制及其用途的高層次概覽:

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

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

圖表顯示 Next.js 中四種機制的預設快取行為,包含建置時和路由首次訪問時的 HIT、MISS 和 SET。

快取行為會根據路由是靜態或動態渲染、資料是否被快取,以及請求是首次訪問還是後續導航而變化。根據您的使用情境,您可以為個別路由和資料請求配置快取行為。

請求記憶化 (Request Memoization)

React 擴展了 fetch API,自動記憶化具有相同 URL 和選項的請求。這意味著您可以在 React 元件樹的多個位置呼叫相同的 fetch 函數,而實際上只會執行一次。

去重複的 Fetch 請求

例如,如果您需要在路由中多處使用相同資料(如在 Layout、Page 和多個元件中),您不必在樹的頂層獲取資料並透過 props 傳遞。相反,您可以在需要資料的元件中直接獲取,而無需擔心因網路重複請求相同資料而影響效能。

async function getItem() {
  // `fetch` 函數會自動記憶化,結果會被快取
  const res = await fetch('https://.../item/1')
  return res.json()
}

// 此函數被呼叫兩次,但僅首次執行
const item = await getItem() // cache MISS

// 第二次呼叫可以位於路由的任何位置
const item = await getItem() // cache HIT

請求記憶化的工作原理

圖表顯示 React 渲染期間 fetch 記憶化的工作方式。
  • 在渲染路由時,首次呼叫特定請求時,其結果不會在記憶體中,因此會是快取 MISS
  • 因此,函數會被執行,資料會從外部來源獲取,結果會儲存在記憶體中。
  • 同一渲染過程中的後續請求函數呼叫會是快取 HIT,資料會從記憶體中回傳,無需再次執行函數。
  • 路由渲染完成後,記憶體會「重置」,所有請求記憶化條目會被清除。

須知事項

  • 請求記憶化是 React 的功能,而非 Next.js 的功能。此處提及是為了展示它與其他快取機制的互動。
  • 記憶化僅適用於 fetch 請求中的 GET 方法。
  • 記憶化僅適用於 React 元件樹,這意味著:
    • 它適用於 generateMetadatagenerateStaticParams、Layouts、Pages 和其他伺服器元件中的 fetch 請求。
    • 它不適用於路由處理器 (Route Handlers) 中的 fetch 請求,因為它們不屬於 React 元件樹的一部分。
  • 對於不適合使用 fetch 的情況(如某些資料庫客戶端、CMS 客戶端或 GraphQL 客戶端),您可以使用 React cache 函數 來記憶化函數。

持續時間

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

重新驗證

由於記憶化不跨伺服器請求共享且僅在渲染期間有效,因此無需重新驗證。

選擇退出

記憶化僅適用於 fetch 請求中的 GET 方法,其他方法如 POSTDELETE 不會被記憶化。此預設行為是 React 的優化,我們不建議選擇退出。

若要管理個別請求,您可以使用 AbortControllersignal 屬性。然而,這不會讓請求退出記憶化,而是中止進行中的請求。

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

資料快取 (Data Cache)

Next.js 內建了資料快取,可持久化資料請求的結果,跨伺服器請求部署。這是因為 Next.js 擴展了原生 fetch API,允許伺服器上的每個請求設定自己的持久化快取語義。

須知事項:在瀏覽器中,fetchcache 選項表示請求如何與瀏覽器的 HTTP 快取互動;在 Next.js 中,cache 選項表示伺服器端請求如何與伺服器的資料快取互動。

預設情況下,使用 fetch 的資料請求會被快取。您可以使用 fetchcachenext.revalidate 選項來配置快取行為。

資料快取的工作原理

圖表顯示快取和非快取的 fetch 請求如何與資料快取互動。快取的請求儲存在資料快取中並被記憶化,非快取的請求從資料來源獲取,不儲存在資料快取中,但會被記憶化。
  • 在渲染期間首次呼叫 fetch 請求時,Next.js 會檢查資料快取是否有快取的回應。
  • 如果找到快取的回應,會立即回傳並記憶化
  • 如果未找到快取的回應,會向資料來源發出請求,結果儲存在資料快取中並記憶化。
  • 對於非快取資料(如 { cache: 'no-store' }),總是從資料來源獲取結果並記憶化。
  • 無論資料是否被快取,請求總是會被記憶化,以避免在 React 渲染過程中重複請求相同資料。

資料快取與請求記憶化的差異

雖然兩種快取機制都透過重複使用快取資料來提升效能,但資料快取跨請求和部署持久化,而記憶化僅在單次請求的生命週期內有效。

透過記憶化,我們減少了同一渲染過程中必須跨越網路界限(從渲染伺服器到資料快取伺服器,如 CDN 或邊緣網路)或資料來源(如資料庫或 CMS)的重複請求數量。透過資料快取,我們減少了對原始資料來源的請求數量。

持續時間

資料快取跨請求和部署持久化,除非您重新驗證或選擇退出。

重新驗證

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

  • 基於時間的重新驗證:在一定時間後重新驗證資料並在新請求時更新。這適用於變更不頻繁且即時性不高的資料。
  • 按需重新驗證:根據事件(如表單提交)重新驗證資料。按需重新驗證可以使用標籤或路徑方式一次重新驗證一組資料。這適用於需要盡快顯示最新資料的情況(如 headless CMS 的內容更新時)。

基於時間的重新驗證

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

// 最多每小時重新驗證一次
fetch('https://...', { next: { revalidate: 3600 } })

或者,您可以使用路由區段配置選項來配置區段中的所有 fetch 請求,或在不使用 fetch 的情況下進行配置。

基於時間的重新驗證的工作原理

圖表顯示基於時間的重新驗證如何運作,在重新驗證期間後,首次請求會回傳過期資料,然後資料會被重新驗證。
  • 首次呼叫帶有 revalidatefetch 請求時,資料會從外部資料來源獲取並儲存在資料快取中。
  • 在指定時間範圍內(如 60 秒)的任何請求都會回傳快取的資料。
  • 時間範圍過後,下一次請求仍會回傳快取的(已過期)資料。
    • Next.js 會在背景觸發資料的重新驗證。
    • 資料成功獲取後,Next.js 會用新資料更新資料快取。
    • 如果背景重新驗證失敗,則保留先前的資料不變。

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

按需重新驗證

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

按需重新驗證的工作原理

圖表顯示按需重新驗證如何運作,重新驗證請求後,資料快取會更新為新資料。
  • 首次呼叫 fetch 請求時,資料會從外部資料來源獲取並儲存在資料快取中。
  • 觸發按需重新驗證時,相應的快取條目會從快取中清除。
    • 這與基於時間的重新驗證不同,後者會在獲取新資料前保留過期資料。
  • 下次請求時會再次是快取 MISS,資料會從外部資料來源獲取並儲存在資料快取中。

選擇退出

對於個別資料獲取,您可以透過將 cache 選項設為 no-store 來選擇退出快取。這意味著每次呼叫 fetch 時都會獲取資料。

// 為個別 `fetch` 請求選擇退出快取
fetch(`https://...`, { cache: 'no-store' })

或者,您也可以使用路由區段配置選項為特定路由區段選擇退出快取。這會影響區段中的所有資料請求,包括第三方函式庫。

// 為路由區段中的所有資料請求選擇退出快取
export const dynamic = 'force-dynamic'

注意:資料快取目前僅適用於頁面/路由,不適用於中介軟體 (middleware)。中介軟體中的任何 fetch 預設都不會被快取。

Vercel 資料快取

如果您的 Next.js 應用程式部署在 Vercel 上,建議閱讀 Vercel 資料快取 文件以更好地理解 Vercel 的特定功能。

完整路由快取 (Full Route Cache)

相關術語

您可能會看到 Automatic Static OptimizationStatic Site GenerationStatic Rendering 這些術語交替使用,指在建置時渲染和快取應用程式路由的過程。

Next.js 會在建置時自動渲染和快取路由。這是一種優化,允許您提供快取的路由,而非每次請求時在伺服器上渲染,從而加快頁面載入速度。

要理解完整路由快取的工作原理,了解 React 如何處理渲染以及 Next.js 如何快取結果很有幫助:

1. 伺服器上的 React 渲染

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

每個區塊的渲染分為兩個步驟:

  1. React 將伺服器元件渲染為一種特殊的資料格式,稱為 React Server Component Payload,專為串流優化。
  2. Next.js 使用 React Server Component Payload 和客戶端元件 JavaScript 指令在伺服器上渲染 HTML

這意味著我們不必等待所有內容渲染完成後才快取工作或發送回應。相反,我們可以在工作完成時串流回應。

什麼是 React Server Component Payload?

React Server Component Payload 是渲染後的 React 伺服器元件樹的緊湊二進位表示。React 在客戶端使用它來更新瀏覽器的 DOM。React Server Component Payload 包含:

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

欲了解更多,請參閱 伺服器元件 文件。

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

完整路由快取的預設行為,顯示靜態渲染路由的 React Server Component Payload 和 HTML 如何在伺服器上被快取。

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

3. 客戶端的 React 水合 (Hydration) 與協調 (Reconciliation)

在請求時,客戶端會進行以下步驟:

  1. 使用 HTML 立即顯示客戶端與伺服器元件的快速非互動式初始預覽。
  2. 使用 React 伺服器元件有效載荷 (Payload) 來協調客戶端與已渲染的伺服器元件樹,並更新 DOM。
  3. 使用 JavaScript 指令來水合 (hydrate) 客戶端元件,使應用程式具有互動性。

4. Next.js 在客戶端的快取機制 (路由快取 Router Cache)

React 伺服器元件有效載荷會儲存在客戶端的路由快取 (Router Cache) 中,這是一個獨立的記憶體快取,按個別路由區段分割。此路由快取用於儲存先前造訪過的路由並預取未來可能訪問的路由,從而提升導航體驗。

5. 後續導航

在後續導航或預取過程中,Next.js 會檢查 React 伺服器元件有效載荷是否已儲存在路由快取中。如果是,則會跳過向伺服器發送新請求。

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

靜態與動態渲染

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

以下圖表展示了靜態與動態渲染路由的差異,以及快取與未快取資料的對比:

靜態與動態渲染如何影響完整路由快取 (Full Route Cache)。靜態路由在構建時或資料重新驗證後被快取,而動態路由則永遠不會被快取

了解更多關於靜態與動態渲染的資訊。

持續時間

預設情況下,完整路由快取 (Full Route Cache) 是持久性的。這意味著渲染輸出會在使用者請求之間被快取。

失效機制

有兩種方式可以讓完整路由快取失效:

  • 重新驗證資料 (Revalidating Data):重新驗證資料快取 (Data Cache) 會連帶使路由快取失效,因為伺服器會重新渲染元件並快取新的渲染輸出。
  • 重新部署 (Redeploying):與資料快取不同(資料快取會在部署之間持續存在),完整路由快取會在每次新部署時被清除。

選擇退出

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

  • 使用動態函式 (Dynamic Function):這會讓路由退出完整路由快取,並在請求時動態渲染。資料快取仍可使用。
  • 使用路由區段配置選項 dynamic = 'force-dynamic'revalidate = 0:這會跳過完整路由快取和資料快取。意味著元件會在每次傳入請求時重新渲染,並從伺服器獲取資料。路由快取仍會生效,因為它是客戶端快取。
  • 選擇退出資料快取 (Data Cache):如果路由有一個未快取的 fetch 請求,這會讓路由退出完整路由快取。針對該特定 fetch 請求的資料會在每次傳入請求時重新獲取。其他未選擇退出快取的 fetch 請求仍會被快取在資料快取中。這允許混合使用快取與未快取的資料。

路由快取 (Router Cache)

相關術語:

您可能會看到路由快取被稱為客戶端快取 (Client-side Cache)預取快取 (Prefetch Cache)。雖然預取快取指的是預取的路由區段,客戶端快取則是指整個路由快取,包括已造訪和預取的區段。 此快取專門適用於 Next.js 和伺服器元件,與瀏覽器的 bfcache 不同,儘管效果類似。

Next.js 有一個記憶體中的客戶端快取,用於儲存 React 伺服器元件有效載荷,按個別路由區段分割,並在使用者會話期間保留。這稱為路由快取。

路由快取的工作原理

路由快取對於靜態與動態路由的運作方式,顯示初始與後續導航的 MISS 和 HIT 狀態。

當使用者在路由之間導航時,Next.js 會快取已造訪的路由區段,並預取 (prefetch) 使用者可能導航到的路由(基於其視窗中的 <Link> 元件)。

這為使用者帶來了更好的導航體驗:

  • 即時的後退/前進導航,因為已造訪的路由被快取,且新路由的導航因預取和部分渲染 (partial rendering) 而快速。
  • 導航之間無需完整頁面重新載入,且 React 狀態和瀏覽器狀態會被保留。

路由快取與完整路由快取的差異

路由快取在使用者會話期間暫時將 React 伺服器元件有效載荷儲存在瀏覽器中,而完整路由快取則在多個使用者請求之間將 React 伺服器元件有效載荷和 HTML 持久儲存在伺服器上。

完整路由快取僅快取靜態渲染的路由,而路由快取同時適用於靜態與動態渲染的路由。

持續時間

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

  • 會話 (Session):快取在導航之間持續存在。但在頁面重新整理時會被清除。
  • 自動失效週期 (Automatic Invalidation Period):佈局 (layouts) 和載入狀態的快取會在特定時間後自動失效。持續時間取決於資源是如何預取 (prefetched) 的,以及資源是否為靜態生成 (statically generated)
    • 預設預取 (Default Prefetching) (prefetch={null} 或未指定):動態頁面不會被快取,靜態頁面快取 5 分鐘。
    • 完整預取 (Full Prefetching) (prefetch={true}router.prefetch):靜態與動態頁面均快取 5 分鐘。

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

須知:實驗性的 staleTimes 配置選項可用於調整上述的自動失效時間。

失效機制

有兩種方式可以讓路由快取失效:

  • 伺服器操作 (Server Action) 中:
  • 呼叫 router.refresh 會讓路由快取失效,並為當前路由向伺服器發送新請求。

選擇退出

無法完全選擇退出路由快取。但您可以通過呼叫 router.refreshrevalidatePathrevalidateTag(見上文)來讓它失效。這會清除快取並向伺服器發送新請求,確保顯示最新資料。

您也可以通過將 <Link> 元件的 prefetch 屬性設為 false 來選擇退出預取 (prefetching)。但這仍會暫時儲存路由區段 30 秒,以允許在嵌套區段(例如標籤欄)之間或前後導航時實現即時導航。已造訪的路由仍會被快取。

快取互動

在配置不同的快取機制時,了解它們如何相互影響非常重要:

資料快取與完整路由快取

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

資料快取與客戶端路由快取

API

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

API路由快取 (Router Cache)完整路由快取 (Full Route Cache)資料快取 (Data Cache)React 快取 (React Cache)
<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 鉤子的 prefetch 選項可用於手動預取路由。這會將 React 伺服器元件有效載荷添加到路由快取中。

參閱 useRouter 鉤子 API 參考。

router.refresh

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

渲染結果會在客戶端進行協調,同時保留 React 狀態和瀏覽器狀態。

參閱 useRouter 鉤子 API 參考。

fetch

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

// 預設快取。`force-cache` 是預設選項,可省略。
fetch(`https://...`, { cache: 'force-cache' })

參閱 fetch API 參考 以獲取更多選項。

fetch options.cache

您可以通過將 cache 選項設為 no-store 來讓個別 fetch 請求選擇退出資料快取:

// 選擇退出快取
fetch(`https://...`, { cache: 'no-store' })

由於渲染輸出依賴於資料,使用 cache: 'no-store' 也會讓使用該 fetch 請求的路由跳過完整路由快取。也就是說,該路由會在每次請求時動態渲染,但您仍可以在同一路由中使用其他快取的資料請求。

參閱 fetch API 參考 以獲取更多選項。

fetch options.next.revalidate

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

// 最多每 1 小時重新驗證一次
fetch(`https://...`, { next: { revalidate: 3600 } })

參閱 fetch API 參考 以獲取更多選項。

fetch options.next.tagsrevalidateTag

Next.js 提供了一個快取標籤系統,用於實現細粒度的資料快取與重新驗證。

  1. 使用 fetchunstable_cache 時,您可以選擇用一個或多個標籤來標記快取項目。
  2. 接著,您可以呼叫 revalidateTag 來清除與該標籤相關的快取項目。

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

// 使用標籤快取資料
fetch(`https://...`, { next: { tags: ['a', 'b', 'c'] } })

然後,呼叫 revalidateTag 並指定標籤以清除快取項目:

// 重新驗證具有特定標籤的項目
revalidateTag('a')

根據您的需求,可以在以下兩個地方使用 revalidateTag

  1. 路由處理器 (Route Handlers) - 用於在第三方事件(例如 Webhook)發生時重新驗證資料。這不會立即使路由快取失效,因為路由處理器並未綁定到特定路由。
  2. 伺服器操作 (Server Actions) - 用於在使用者操作(例如表單提交)後重新驗證資料。這將使相關路由的路由快取失效。

revalidatePath

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

revalidatePath('/')

根據您的需求,可以在以下兩個地方使用 revalidatePath

  1. 路由處理器 (Route Handlers) - 用於在第三方事件(例如 Webhook)發生時重新驗證資料。
  2. 伺服器操作 (Server Actions) - 用於在使用者互動(例如表單提交、點擊按鈕)後重新驗證資料。

更多資訊請參閱 revalidatePath API 參考文件

revalidatePathrouter.refresh 的比較:

呼叫 router.refresh 會清除路由快取 (Router Cache),並在伺服器上重新渲染路由區段,但不會使資料快取 (Data Cache) 或完整路由快取 (Full Route Cache) 失效。

兩者的區別在於,revalidatePath 會清除資料快取和完整路由快取,而 router.refresh() 不會改變資料快取和完整路由快取,因為它是一個客戶端 API。

動態函式

動態函式如 cookiesheaders,以及 Pages 中的 searchParams 屬性,依賴於運行時傳入的請求資訊。使用這些函式會使路由退出完整路由快取 (Full Route Cache),換句話說,該路由將被動態渲染。

cookies

在伺服器操作 (Server Action) 中使用 cookies.setcookies.delete 會使路由快取失效,以防止使用 cookies 的路由變得過時(例如反映認證狀態的變更)。

更多資訊請參閱 cookies API 參考文件

路由區段配置選項

路由區段配置選項可用於覆蓋路由區段的預設值,或在無法使用 fetch API 時(例如使用資料庫客戶端或第三方函式庫)使用。

以下路由區段配置選項將使路由退出資料快取 (Data Cache) 和完整路由快取 (Full Route Cache):

  • const dynamic = 'force-dynamic'
  • const revalidate = 0

更多選項請參閱 路由區段配置 (Route Segment Config) 文件。

generateStaticParams

對於 動態路由區段 (dynamic segments)(例如 app/blog/[slug]/page.js),由 generateStaticParams 提供的路徑會在構建時快取在完整路由快取 (Full Route Cache) 中。在請求時,Next.js 也會在首次訪問時快取那些在構建時未知的路徑。

您可以在路由區段中使用 export const dynamicParams = false 選項來禁用請求時的快取。當使用此配置選項時,只有由 generateStaticParams 提供的路徑會被服務,其他路由將返回 404 或匹配(在 萬用路由 (catch-all routes) 的情況下)。

更多資訊請參閱 generateStaticParams API 參考文件

React cache 函式

React cache 函式允許您記憶化函式的返回值,從而可以在多次呼叫同一函式時僅執行一次。

由於 fetch 請求會自動記憶化,您無需將其包裹在 React cache 中。然而,在 fetch API 不適用的情況下,您可以使用 cache 手動記憶化資料請求。例如,某些資料庫客戶端、CMS 客戶端或 GraphQL 客戶端。

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
})