Next.js 的快取機制
Next.js 透過快取渲染工作和資料請求來提升應用程式效能並降低成本。本頁將深入探討 Next.js 的快取機制、您可以用來設定它們的 API,以及它們之間如何相互作用。
注意事項:本頁可協助您了解 Next.js 的底層運作方式,但對於有效使用 Next.js 並非必要知識。大多數 Next.js 的快取策略取決於您的 API 使用方式,並已設定預設值,以便在零或最小設定下獲得最佳效能。如果您想直接查看範例,請由此開始。
概覽
以下是不同快取機制及其用途的高階概覽
機制 | 內容 | 位置 | 用途 | 持續時間 (Duration) |
---|---|---|---|---|
請求記憶體化 (Request Memoization) | 函式的回傳值 | 伺服器 | 在 React 元件樹中重複使用資料 | 每次請求的生命週期 |
資料快取 (Data Cache) | 資料 | 伺服器 | 跨使用者請求和部署儲存資料 | 永久性(可重新驗證) |
完整路由快取 (Full Route Cache) | HTML 和 RSC 負載 | 伺服器 | 降低渲染成本並提升效能 | 永久性(可重新驗證) |
路由器快取 | RSC 負載 | 客戶端 | 減少導覽時的伺服器請求 | 使用者工作階段或基於時間 |
預設情況下,Next.js 會盡可能快取更多內容以提升效能並降低成本。這表示除非您選擇停用,否則路由會進行靜態渲染,而資料請求會被快取。下圖顯示了預設的快取行為:在建置時靜態渲染路由,以及首次訪問靜態路由時。


快取行為會根據路由是靜態渲染還是動態渲染、資料是否已快取,以及請求是初始訪問的一部分還是後續導覽而有所不同。您可以根據您的使用案例,為個別路由和資料請求設定快取行為。
請求記憶體化
React 擴充了 fetch
API,以自動記憶體化具有相同 URL 和選項的請求。這表示您可以在 React 元件樹中的多個位置呼叫具有相同資料的 fetch 函式,但只會執行一次。


例如,如果您需要在整個路由中使用相同的資料(例如,在佈局、頁面和多個元件中),您不需要在樹的頂部擷取資料,並在元件之間傳遞 props。您可以直接在需要資料的元件中擷取資料,而不必擔心在網路上針對相同資料發出多個請求的效能影響。
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
請求記憶化 (Request Memoization) 的運作方式


- 在渲染路由的過程中,第一次呼叫特定請求時,其結果不會儲存在記憶體中,這將會是快取 `MISS` (未命中)。
- 因此,函式將會被執行,資料將會從外部來源取得,然後結果將會儲存在記憶體中。
- 在同一次渲染過程中,後續對該請求的函式呼叫將會是快取 `HIT` (命中),並且資料將會從記憶體中返回,而不會再次執行函式。
- 一旦路由渲染完成,且渲染過程結束,記憶體就會被「重置」,所有請求記憶化項目都會被清除。
注意事項:
- 請求記憶化是 React 的功能,而不是 Next.js 的功能。這裡包含它是為了說明它與其他快取機制的互動方式。
- 記憶化僅適用於 `fetch` 請求中的 `GET` 方法。
- 記憶化僅適用於 React 元件樹,這表示:
- 它適用於 `generateMetadata`、`generateStaticParams`、佈局 (Layouts)、頁面 (Pages) 和其他伺服器元件 (Server Components) 中的 `fetch` 請求。
- 它不適用於路由處理程式 (Route Handlers) 中的 `fetch` 請求,因為它們不是 React 元件樹的一部分。
- 對於 `fetch` 不適合的情況(例如某些資料庫客戶端、CMS 客戶端或 GraphQL 客戶端),您可以使用 React 的 `cache` 函式 來記憶化函式。
持續時間
快取的持續時間為伺服器請求的生命週期,直到 React 元件樹完成渲染為止。
重新驗證
由於記憶化不會跨伺服器請求共用,且僅在渲染期間套用,因此不需要重新驗證。
停用
記憶化僅適用於 `fetch` 請求中的 `GET` 方法,其他方法(例如 `POST` 和 `DELETE`)則不會被記憶化。此預設行為是 React 的一種優化,我們不建議停用它。
要管理個別請求,您可以使用 signal
屬性,該屬性來自 AbortController
。然而,這不會讓請求退出記憶體化,而是中止進行中的請求。
const { signal } = new AbortController()
fetch(url, { signal })
資料快取
Next.js 有一個內建的資料快取,可以將資料擷取的結果持久化,跨越不同的連入伺服器請求和部署。這是可行的,因為 Next.js 擴展了原生的 fetch
API,允許伺服器上的每個請求設定自己的持久化快取語義。
注意事項:在瀏覽器中,
fetch
的cache
選項指示請求將如何與瀏覽器的 HTTP 快取互動;在 Next.js 中,cache
選項指示伺服器端請求將如何與伺服器的資料快取互動。
您可以使用 fetch
的 cache
和 next.revalidate
選項來設定快取行為。
資料快取的運作方式


- 在渲染期間第一次呼叫帶有
'force-cache'
選項的fetch
請求時,Next.js 會檢查資料快取中是否有快取的回應。 - 如果找到快取的回應,它會立即返回並進行記憶體化。
- 如果沒有找到快取的回應,則會向資料來源發出請求,將結果儲存在資料快取中,並進行記憶體化。
- 對於未快取的資料(例如,未定義
cache
選項或使用{ cache: 'no-store' }
),結果會始終從資料來源擷取,並進行記憶體化。 - 無論資料是否已快取,請求都會始終進行記憶體化,以避免在 React 渲染過程中對相同資料發出重複的請求。
資料快取和請求記憶體化之間的差異
雖然兩種快取機制都透過重複使用快取的資料來協助提升效能,但資料快取會在連入請求和部署之間持久化,而記憶體化只會持續一個請求的生命週期。
持續時間
除非您重新驗證或選擇退出,否則資料快取會在連入請求和部署之間持久化。
重新驗證
快取的資料可以透過兩種方式重新驗證:
- 基於時間的重新驗證:在經過一段時間後,當新的請求發出時,重新驗證資料。這適用於不常變更且新鮮度不那麼重要的資料。
- 按需重新驗證:根據事件(例如表單提交)重新驗證資料。按需重新驗證可以使用基於標籤或基於路徑的方法一次重新驗證多組資料。當您想要確保盡快顯示最新資料時,這非常有用(例如,當您的無頭 CMS 中的內容更新時)。
基於時間的重新驗證
要以定時的時間間隔重新驗證資料,您可以使用 fetch
的 next.revalidate
選項來設定資源的快取存期(以秒為單位)。
// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })
或者,您可以使用路由區段設定選項來設定區段中所有 fetch
請求,或是在您無法使用 fetch
的情況下進行設定。
基於時間的重新驗證如何運作


- 第一次呼叫帶有
revalidate
的 fetch 請求時,資料將會從外部資料來源擷取並儲存在資料快取中。 - 在指定的時間範圍內(例如 60 秒)呼叫的任何請求都將返回快取的資料。
- 時間範圍過後,下一個請求仍然會返回快取的(現在已過期的)資料。
- Next.js 會在背景觸發資料的重新驗證。
- 一旦資料成功擷取,Next.js 就會使用新的資料更新資料快取。
- 如果背景重新驗證失敗,先前的資料將保持不變。
這類似於 stale-while-revalidate(過期時重新驗證) 的行為。
隨需重新驗證
可以透過路徑(revalidatePath
)或快取標籤(revalidateTag
)隨需重新驗證資料。
隨需重新驗證如何運作


- 第一次呼叫
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 邊界。
每個區塊都分兩個步驟渲染:
- React 將伺服器組件渲染成一種特殊的資料格式,稱為React 伺服器組件有效負載 (React Server Component Payload),該格式已針對串流進行優化。
- Next.js 使用 React 伺服器組件有效負載和客戶端組件 JavaScript 指令在伺服器上渲染 HTML。
這表示我們不必等到所有內容都渲染完畢才能快取工作或傳送回應。相反地,我們可以在工作完成時串流傳送回應。
什麼是 React 伺服器組件有效負載?
React 伺服器組件有效負載是已渲染的 React 伺服器組件樹的精簡二進位表示法。React 在客戶端上使用它來更新瀏覽器的 DOM。React 伺服器組件有效負載包含:
- 伺服器組件的渲染結果
- 客戶端組件應渲染位置的佔位符及其 JavaScript 檔案的參考
- 從伺服器組件傳遞到客戶端組件的任何 props
欲了解更多資訊,請參閱伺服器組件文件。
2. 伺服器端的 Next.js 快取(完整路由快取)
<Link>
預設情況下,<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
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })