時間過得很快,也來到本系列文章的倒數第二篇。
昨天介紹了 Next 快取機制的前兩層 - Data Cache 和 Request Memoization。兩者主要會在 Server Components 進行 data fetching 時觸發。
快速複習一下兩者的目的:
Data Cache:減少向 data source 索取相同資料的次數
Request Memoization:避免重複處理相同 requests
假如還不認識兩者的讀者,建議先閱讀
Day 28
的文章。
昨天似乎成功透過禁止 Data Cache 和 Request Memoization,解決文章開頭,每頁用戶代碼一樣的問題。
但假如我現在做個小改動:一樣禁用 Data Cache,可是把
<a>
改成
<Link>
,來看看改成 soft navigation 後,會不會發生什麼改變:
不要懷疑,你沒有眼花,也不是重複播放的關係,三個頁面的用戶代碼的確不一樣,但第二輪和第三輪拜訪,用戶代碼卻還是跟第一輪一樣。所以影片就一直在 213 -> 243 -> 241 循環。
明明禁用 Data Cache 了,為什麼會這樣呢?我們接著往快取層上層找,看看到底是誰在搞鬼吧!
Full Route Cache
顧名思義,Full Route Cache 就是快取整個 route segment。在 build time,Next 會先渲染靜態路由,並快取渲染結果,避免用戶每次拜訪頁面時,都要執行 server-side rendering,加速頁面載入速度。類似我們
Day 03
提到的 ISR 作法。
所以當 server 接到要渲染 route segment 的 request 時,會先檢查快取有沒有這個 route segment 渲染結果 ( HTML & RSC Payload)。
假如不知道什麼是 RSC Payload,可參考
Day 12 文章
假如有,就直接回覆快取內容;假如沒有,就會進行渲染。渲染時假如有 fetch requests,就會檢查有沒有 Reqeust Memoization,沒有才進行 data fetching。進行 data fetching 時,會檢查有沒有 Data Cache,沒有才向 data source 拿資料。
這樣就將 Full Route Cache 和昨天的內容串起來啦!提供官方文件的 infographic 給大家參考:
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#2-nextjs-caching-on-the-server-full-route-cache )
禁用 Full Route Cache
上述有提到,Next 只會快取靜態渲染的內容,所以只要是
動態渲染
就不會觸發 Full Route Cache。
動態渲染的意思是,需要在 run-time 根據 request 決定渲染內容。像是使用 cookies、searchParams 等功能 ( dynamic function ),Next 就會自動轉成動態渲染,route segment 就不會在 build-time 渲染。
假如是動態渲染,server 收到渲染 request 時會略過 Full Route Cache 的檢查,直接進行渲染。
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#2-nextjs-caching-on-the-server-full-route-cache )
要觸發動態渲染,除了使用 dynamic functions 外,還有幾個方法:
調整 route segment 設定
可以使用
revalidate = 0
或
dynamic = 'force-dynamic'
來強迫路由動態渲染:
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export default async function Page() {
return (
使用兩者,Full Route Cache 和 Data Cache 都不會觸發。
還有其他 route segment 設定可以使用,像是 Day 19 提到的 dynamicParams
。有興趣的讀者可參考官方文件。
禁用 Data Cache
假如某個 fetch request 使用昨天提到的 cache='no-store'
禁用 Data Cache,也會連帶禁用 Full Route Cache。
但禁用某個 fetch request 的 Data Cache 並不會影響其他 fetch request 的 Data Cache。 一樣做個小實驗:
我們在 /users fetch 兩次 /api/hello ,並讓其中一個 request 帶 {cache: 'no-store'}
,接著渲染這兩個 response:
/* app/users/page.tsx */
const fetchUserId = async (url: string, option?: RequestInit) => {
const res = await fetch(url, option);
const jsonData = await res.json();
return jsonData.message;
export default async function Page() {
const url = 'http://localhost:3000/api/hello';
const no_cache_option = { cache: 'no-store' };
const userId_cache = await fetchUserId(url);
const userId_no_cache = await fetchUserId(
no_cache_option as RequestInit
return (
<div className='...'>
<h1>{userId_cache}</h1>
<h1>{userId_no_cache}</h1>
完成後,我們重新整理幾次頁面,看看會發生什麼事:
會發現,只有第二個用戶代碼隨著重新整理變化。所以雖然第二個 request 禁用 Data Cache,Full Route Cache 也隨之禁用,但並不影響第一個 request 的 Data Cache。
重置 Full Route Cache
當 Data Cache 重置時,也會清除 Full Route Cach;除此之外,重新部署也會清除 Full Route Cache。
Data Cache 除非有設定重置條件或主動重置,不然會永久存在。就算重新部署也不會清除。
回到開頭的問題,很顯然問題也不在 Full Route Cache,因為禁用 Data Cache 就不會觸發 Full Route Cache。
所以只剩最後一個選項 - Router Cache。在開始進一步調查之前,先來快速認識 Router Cache:
Router Cache
Router Cache 是 Next 在瀏覽器的 in-memory cache,專門負責存 route segment 的 RSC Payload。
當用戶在做 soft navigation 時,Next 渲染完目前 route segment 的內容,會快取 RSC Payload,以加快重複拜訪時,頁面載入的速度。
也適用 Prefetched 的路由
Day 20 有提到,<Link>
預設會提前載入目的地的資源。假如沒有禁用 prefetch,prefetched 路由的 RSC Payload 也會快取。
![](https://nextjs.org/_next/image?url=%2Fdocs%2Fdark%2Frouter-cache.png&w=3840&q=75&dpl=dpl_A6R6N81SDBFj95jak4AWkZuBmAgN)
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching#router-cache )
用戶重新整理頁面,會清除所有 Router Cache
使用 revalidatePath
或 revalidateTag
( 可參考昨天文章 )
使用 router.refresh
( 可參考昨天文章 )
Route Handler 或 Server Acions 中使用 cookies.set 或 cookies.delete
到達自動失效時間:靜態渲染為 30 秒,動態渲染為 5 分鐘,時間到會清除該 route segment 的 Router Cache
補充一下,雖然官方文件說 Route Handler 可以使用官方 API 的 cookies.set
function,但根據 issue#52799 官方的說法,cookies.set
無法在 rendering 時使用。
意思是,假如要在 Route Handler 使用 cookies.set
,要直接在瀏覽器拜訪 API 連結,或用 postman 打這支 API 才有效,小弟實測也確實如此,還蠻玄的...
所以假如要使用 cookies.set
,官方建議在 middleware 或 Server Actions 中使用。
快速認識 Router Cache 後,我們打開 DevTools 的 Network,觀察一下路由切換時的是否有向 server 取 RSC Payload:
![](https://github.com/ShihYuChang/next-app-router-template/blob/main/static/router_cache_navigation.gif?raw=true)
可以觀察到,當我們進到第一次 /welcome、/dashbaord、/shop 時,會向 server 取對應的 RSC Payload,但後續再次拜訪,就不會再取一次。
假如仔細觀察上方的影片,會發現,用戶代碼也是第二輪開始停止更新,與停止載入新的 RSC Payload 的時機一樣。
假如回顧昨天的文章,的確使用 <a>
製造 hard navigation,或使用 revalidatePath
與 router.refresh
都可以讓用戶代碼更新。看樣子開頭的問題,確實是 Router Cache 造成的。
那我們有辦法直接禁用它嗎?
禁用 Router Cache
我們來看官方怎麼說:
It's not possible to opt out of the Router Cache.
很遺憾地,我們無法禁用 Router Cache。所以假如希望頁面切換時,能自動清除 Router Cache,目前只能捨棄 <Link>
改用原生的 <a>
。
四種快取機制回顧
認識完四個快取機制後,最後我們再回顧一次昨天前言的圖:
![](https://nextjs.org/_next/image?url=%2Fdocs%2Fdark%2Fcaching-overview.png&w=3840&q=75&dpl=dpl_CqBNtqf2KK3XDYM1YF6HAteG9s45)
( 圖片來源:https://nextjs.org/docs/app/building-your-application/caching )
簡單來說,當使用者進到頁面,假如有 Server Components:
檢查有沒有 Router Cache,有就用 Router Cache 中的 RSC Payload 渲染畫面;沒有的話,則請求 Server 渲染。
Server 收到請求後,先檢查有沒有 Full Route Cache,有就直接回覆快取的渲染結果;沒有則進行渲染。
渲染過程假如有 fetch request,先檢查有沒有 Request Memoization,有的話直接回覆快取的 response;沒有則執行 data fetching。
執行 data fetching 時,先檢查有沒有 Data Cache,有則直接回覆快取的資料;沒有則到 data source 撈資料。
以上,就是 Next.js 13 快取機制的介紹,Next.js 13 的介紹也到這邊告一段落。希望有幫助到大家,在開發 Next 專案時,工具技術的選擇上,能多一些參考依據,以及 debug 時,能多一些線索。
大致認識完 Next.js 13 的基本功能後,下一步呢?這部分就留到最後一天和大家分享囉。
謝謝大家耐心的閱讀,我們明天見!