问题:静态与动态的权衡
传统上,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 应用程序中的默认渲染策略。

