問題:靜態與動態的權衡
傳統上,Next.js 開發人員面臨著一個二元選擇:靜態生成(快速但陳舊)或伺服器端渲染(新鮮但較慢)。部分預先渲染(PPR)透過將兩種方法結合在一個頁面中消除了這種權衡——靜態 HTML shell 立即串流傳輸,而動態內容透過 React Suspense 載入。
PPR 的工作原理
PPR 的工作原理是使用 React Suspense 識別靜態和動態邊界。在建置期間,Next.js 預先渲染靜態 shell。在請求時,它會用串流動態內容取代 Suspense 回退:
// page.tsx — PPR enabled
import { Suspense } from "react";
import { Header } from "@/components/header";
import { PersonalizedGreeting } from "@/components/greeting";
import { ProductList } from "@/components/products";
export default function HomePage() {
return (
<div>
{/* Static — prerendered at build time */}
<Header />
{/* Dynamic — streamed at request time */}
<Suspense fallback={<GreetingSkeleton />}>
<PersonalizedGreeting />
</Suspense>
{/* Static — prerendered at build time */}
<ProductList />
</div>
);
}
配置
在 next.config.ts 中啟用 PPR:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
ppr: true,
},
};
export default nextConfig;
對於路由層級控制,匯出 experimental_ppr 配置:
// app/page.tsx
export const experimental_ppr = true;
靜態外殼和流邊界
當請求 PPR 頁面時,瀏覽器立即收到:
- 靜態 shell — 用於非暫停元件、CSS 和關鍵資源的 HTML
- 懸念回退 - 動態內容載入時顯示佔位符 UI
- 串流動態內容 — 當每個 Suspense 邊界解析時,其 HTML 會被串流傳輸
Timeline:
Request → Static shell (instant) → Fallback UI shown
→ Dynamic content A arrives → UI updated
→ Dynamic content B arrives → UI updated
→ Page fully interactive
性能優勢
| 公制 | 固態繼電器 | 靜態 | PP-R |
|---|---|---|---|
| 第一個位元組 (TTFB) | 慢(伺服器運算) | 快速(CDN 邊緣) | 快速(CDN 邊緣) |
| 首次內容繪製 (FCP) | 中 | 快 | 快 |
| 互動時間 | 慢 | 快 | 快 |
| 動態資料新鮮度 | 新鮮 | 陳舊 | 新鮮 |
| 每個請求的伺服器負載 | 高 | 最小 | 低 |
使用案例
PPR 非常適合將靜態內容與個人化或即時元素結合的頁面:
電子商務產品頁面:
- 靜態:產品描述、圖像、評論(已快取)
- 動態:庫存狀態、價格(特定於使用者)、建議
儀表板應用程式:
- 靜態:導覽、側邊欄、佈局外殼
- 動態:用戶特定的指標、通知、即時數據
內容網站:
- 靜態:文章正文、元資料、SEO 標籤
- 動態:使用者評論、相關文章(個人化)、訂閱狀態
// Product page with PPR
export default function ProductPage({ params }: { params: { id: string } }) {
return (
<>
{/* Static — cached at edge */}
<ProductImages id={params.id} />
<ProductDescription id={params.id} />
{/* Dynamic — streamed per user */}
<Suspense fallback={<PriceSkeleton />}>
<UserSpecificPrice id={params.id} />
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<PersonalizedRecommendations id={params.id} />
</Suspense>
</>
);
}
搜尋引擎優化的影響
PPR 頁面完全可索引,因為靜態 shell 包含所有 SEO 關鍵內容。動態部分逐漸增強,但搜尋引擎爬蟲接收完整的靜態內容。
- 元標籤:完全預先渲染
- 開放圖影像:靜態
- 主要內容:靜態,動態增強
- 結構化資料:包含在靜態 shell 中
PPR 與其他渲染策略
| 戰略 | 建置時間 | 請求時間 | 邊緣快取 |
|---|---|---|---|
| 靜態(SSG) | 所有頁面 | 什麼都沒有 | 整頁 |
| 固態繼電器 | 什麼都沒有 | 整個頁面 | 按請求 |
| 情監偵 | 大多數頁面 | 重新驗證 | 整頁(重新驗證時陳舊) |
| PP-R | 靜態外殼 | 動態邊界 | Shell + 串流媒體 |
結論
部分預渲染代表了我們對渲染策略的思考方式的根本轉變。 PPR 沒有在靜態速度和動態新鮮度之間進行選擇,而是為我們提供了兩者。透過將動態內容包裝在 Suspense 邊界中,開發人員可以提供即時靜態 shell,同時保持個人化功能的即時互動性。隨著採用率的成長,PPR 很可能成為 Next.js 應用程式中的預設渲染策略。

