Next.js 部分預渲染 (Partial Prerendering, PPR) 與快取 use cache

Next.js 引入了兩個革命性的功能,旨在打破「靜態」與「動態」的二元對立。透過 部分預渲染 (Partial Prerendering, PPR)use cache 指令,你可以構建出載入速度極快且資料即時的頁面。

部分預渲染 (PPR)

在過去,一個頁面要嘛是靜態的(SSG),要嘛是動態的(SSR)。如果頁面中有一小部分是動態的(例如購物車數量),整個頁面可能就得變成動態渲染,這會犧牲首屏載入速度。

PPR 解決了這個問題: 它允許你在同一個頁面中,將「靜態外殼」預先編譯好,而將「動態部分」包裹在 React Suspense 中。

如何運作?

  1. 靜態外殼:這部分在編譯時就產生,使用者點擊時會秒速顯示(例如導航列、產品說明)。
  2. 動態洞 (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(指令),它標誌著快取機制的重大典範轉移。不同於以往需要手動設定 fetchnext: { 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>;
}

精細控制:cacheTagcacheLife

單純的快取是不夠的,我們還需要控制「何時過期」以及「如何清除」。

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:

ProfileStale Time (用戶看到舊資料的時間)Revalidate (背景更新頻率)適用場景
seconds0s (即時)10s直播、即時股價
minutes0s (即時)1m留言板、論壇列表
hours5m1h部落格文章、新聞
days1d1d產品目錄、說明文件
weeks1w1w幾乎不變的靜態內容
max1mo1mo永久存檔

| 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 開發進入了新的效能時代。