Next.js 部分預渲染 (Partial Prerendering, PPR) 與快取 use cache
Next.js 引入了兩個革命性的功能,旨在打破「靜態」與「動態」的二元對立。透過 部分預渲染 (Partial Prerendering, PPR) 與 use cache 指令,你可以構建出載入速度極快且資料即時的頁面。
部分預渲染 (PPR)
在過去,一個頁面要嘛是靜態的(SSG),要嘛是動態的(SSR)。如果頁面中有一小部分是動態的(例如購物車數量),整個頁面可能就得變成動態渲染,這會犧牲首屏載入速度。
PPR 解決了這個問題: 它允許你在同一個頁面中,將「靜態外殼」預先編譯好,而將「動態部分」包裹在 React Suspense 中。
如何運作?
- 靜態外殼:這部分在編譯時就產生,使用者點擊時會秒速顯示(例如導航列、產品說明)。
- 動態洞 (Dynamic Holes):被 Suspense 包裹的部分會在執行時異步加載,填補進靜態外殼中。

設定開發:
目前 PPR 可以在 next.config.js 中開啟:
// next.config.mjs
const nextConfig = {
experimental: {
ppr: 'incremental', // 逐步開啟 PPR 功能
},
};
程式碼範例:
import { Suspense } from 'react';
import { StaticSkeleton, DynamicComponent } from './components';
export default function Page() {
return (
<main>
<h1>我的產品頁面 (靜態標題)</h1>
<section>
<StaticSkeleton />
{/* 動態部分被 Suspense 隔離 */}
<Suspense fallback={<p>載入推薦中...</p>}>
<DynamicComponent />
</Suspense>
</section>
</main>
);
}
Next.js 會自動觀察你的程式碼:
- 沒被 Suspense 包起來的部分: Next.js 會在編譯時(Build time)就把它們做成靜態 HTML。
- 被 Suspense 包起來的部分: Next.js 會知道這是「動態黑洞」,先放個 fallback(載入中狀態),等網頁打開後再把資料串流過來。
如果你在 Suspense 之外使用了動態函式(例如 cookies()、headers() 或搜尋參數 searchParams),Next.js 就沒辦法幫你預渲染「外殼」,因為它不知道現在是誰在看網頁,這時整頁就會退化成傳統的 SSR(動態渲染)。
use cache 指令
use cache 是 Next.js 引入的 React Directive(指令),它標誌著快取機制的重大典範轉移。不同於以往需要手動設定 fetch 的 next: { revalidate: ... } 或使用笨重的 unstable_cache 包裹函式,use cache 允許你以聲明式 (Declarative) 的方式,直接「宣告」某個函式或頁面的計算結果是可以被快取的。
核心概念
當你在一個非同步函式 (Async Function) 或是整個檔案的頂部加上 'use cache' 時,Next.js 會自動將該函式的回傳結果儲存起來 (Memoization)。
- 輸入 (Input):函式的參數 (Arguments)。
- 輸出 (Output):函式的回傳值 (Return Value)。
- 機制:只要傳入相同的參數,Next.js 就會跳過執行,直接回傳快取中的結果。
兩種使用範圍 (Scope)
1. 函式層級 (Function Level)
你可以只針對特定的耗時操作(如資料庫查詢、複雜運算)進行快取,而不影響其他部分。
import { cacheTag } from 'next/cache';
export async function getProductStock(sku: string) {
'use cache'; // <--- 宣告此函式需快取
// 這裡可以是資料庫查詢,或是不支援 fetch cache 的 SDK 呼叫
const stock = await db.stock.findUnique({ where: { sku } });
return stock;
}
2. 檔案層級 (File Level)
如果你在檔案的最上方(import 之前)加上 'use cache',則該檔案內 所有導出 (Export) 的函式(包含 Page Component、Layout、Server Actions 等)都會自動被快取。這非常適合用來建立完全靜態的頁面或 API 路由。
'use cache'; // <--- 整個檔案都快取
export default async function Page() {
const data = await fetchBigData();
return <main>...</main>;
}
精細控制:cacheTag 與 cacheLife
單純的快取是不夠的,我們還需要控制「何時過期」以及「如何清除」。
1. 設定標籤 (cacheTag):用於手動清除
透過 cacheTag,為快取貼上標籤。當資料變更時,你可以使用 revalidateTag 精準清除特定的快取。
import { cacheTag } from 'next/cache';
export async function getUserProfile(userId: string) {
'use cache';
cacheTag(`user-${userId}`); // 貼上 user-123 的標籤
return await db.user.find(userId);
}
後續 Server Action 更新資料時:
import { revalidateTag } from 'next/cache';
async function updateUser(userId: string, data: any) {
await db.user.update(userId, data);
revalidateTag(`user-${userId}`); // 清除舊快取
}
2. 設定壽命 (cacheLife):用於自動過期
cacheLife 允許你設定基於時間的過期策略。Next.js 預設提供了一些語意化的設定檔 (Profiles),你不需要再去計算幾分幾秒。
import { cacheLife } from 'next/cache';
export async function getDailyReport() {
'use cache';
cacheLife('hours'); // 設定生命週期為「小時級別」 (預設 stale: 1小時)
return await generateReport();
}
常見的 Profiles:
| Profile | Stale Time (用戶看到舊資料的時間) | Revalidate (背景更新頻率) | 適用場景 |
|---|---|---|---|
seconds | 0s (即時) | 10s | 直播、即時股價 |
minutes | 0s (即時) | 1m | 留言板、論壇列表 |
hours | 5m | 1h | 部落格文章、新聞 |
days | 1d | 1d | 產品目錄、說明文件 |
weeks | 1w | 1w | 幾乎不變的靜態內容 |
max | 1mo | 1mo | 永久存檔 |
| max | 1mo | 1mo | 永久存檔 |
自定義 cacheLife 設定
如果你覺得預設的設定不符合需求,可以在 next.config.js 中定義自己的快取規則:
// next.config.mjs
const nextConfig = {
experimental: {
cacheLife: {
// 自定義一個名為 'blog' 的 profile
blog: {
stale: 3600, // 1 小時內都算新鮮 (Fresh),直接回傳快取
revalidate: 7200, // 超過 1 小時但未滿 2 小時,視為舊資料 (Stale),
// 允許先回傳舊資料,同時背景更新
expire: 86400, // 超過 24 小時,資料完全作廢,
// 強制等待重新抓取 (Blocking)
},
},
},
};
export default nextConfig;
參數定義:
- stale: 在這段時間內,資料被視為「新鮮」的,不會觸發任何更新請求。
- revalidate: 資料過期後,還能被允許當作「舊資料 (Stale)」回傳給使用者的時間。這期間會採取 SWR 模式(先給舊的,背景更新)。
- expire: 資料的最長壽命。一旦超過這個時間,快取會被強行刪除,下一個請求必須等待新資料抓取完成(Blocking)才能得到回應。
use cache vs unstable_cache
| 特性 | use cache (推薦) | unstable_cache (舊版) |
|---|---|---|
| 語法 | 原生 JavaScript 字串指令 | 需用高階函式包裹 (Higher-order function) |
| 可讀性 | 高,像 async/await 一樣自然 | 低,巢狀結構較深 |
| 參數序列化 | 自動處理 | 自動處理 |
| 未來支援 | Next.js 重點發展方向 | 僅作為過渡 API |
結論:在 Next.js 16+ 專案中,請優先使用 use cache。
小結
- PPR:讓你的頁面同時擁有靜態的快與動態的活。
use cache:讓開發者用最直觀的方式控制資料快取,不再被複雜的快取規則搞混。
這兩個功能的結合,標誌著 Web 開發進入了新的效能時代。