如何使用部分预渲染 (Partial Prerendering)
部分预渲染 (PPR) 是一种渲染策略,允许您在同一路由中结合静态与动态内容。这既能提升初始页面性能,又能支持个性化动态数据。

当用户访问路由时:
- 服务器发送包含静态内容的 外壳 (shell),确保快速初始加载。
- 外壳为动态内容预留 空洞 (holes),这些内容将异步加载。
- 动态空洞会 并行流式传输,减少页面整体加载时间。
🎥 观看视频: PPR 原理及工作方式 → YouTube (10分钟)。
部分预渲染如何工作?
理解部分预渲染前,需熟悉 Next.js 提供的渲染策略。
静态渲染
静态渲染会提前生成 HTML——在构建时或通过 重新验证 完成。结果会被缓存并在用户和请求间共享。
在部分预渲染中,Next.js 会为路由预渲染 静态外壳,可包含布局及不依赖请求时数据的组件。
动态渲染
动态渲染在 请求时 生成 HTML,允许基于请求时数据提供个性化内容。
组件在以下情况下会变为动态:
- 使用
cookies
- 使用
headers
- 使用
connection
- 使用
draftMode
- 使用
searchParams
属性 - 使用
unstable_noStore
- 使用
{ cache: 'no-store' }
选项的fetch
在部分预渲染中,使用这些 API 会触发特殊的 React 错误,提示 Next.js 该组件无法静态渲染,导致构建错误。您可以用 Suspense 边界包裹组件以延迟渲染至运行时。
Suspense
React 的 Suspense 用于延迟渲染部分应用直至条件满足。
在部分预渲染中,Suspense 用于标记组件树中的 动态边界。
构建时,Next.js 会预渲染静态内容和 fallback
UI。动态内容会 延迟 至用户请求路由时加载。
用 Suspense 包裹组件不会使组件本身变为动态(API 使用决定动态性),而是作为封装动态内容并启用 流式传输 的边界。
import { Suspense } from 'react'
import StaticComponent from './StaticComponent'
import DynamicComponent from './DynamicComponent'
import Fallback from './Fallback'
export const experimental_ppr = true
export default function Page() {
return (
<>
<StaticComponent />
<Suspense fallback={<Fallback />}>
<DynamicComponent />
</Suspense>
</>
)
}
流式传输
流式传输将路由拆分为多个块,并在准备就绪时逐步流式传输至客户端。这让用户能在全部内容渲染完成前立即看到部分页面。

在部分预渲染中,包裹在 Suspense 中的动态组件会从服务器并行流式传输。

为减少网络开销,完整响应(包括静态 HTML 和流式动态部分)通过 单次 HTTP 请求 发送。这避免了额外往返,提升了初始加载和整体性能。
启用部分预渲染
通过将 ppr
选项加入 next.config.ts
文件启用 PPR:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
ppr: 'incremental',
},
}
'incremental'
值允许您为特定路由启用 PPR:
export const experimental_ppr = true
export default function Layout({ children }: { children: React.ReactNode }) {
// ...
}
export const experimental_ppr = true
export default function Layout({ children }) {
// ...
}
未设置 experimental_ppr
的路由默认为 false
,不会使用 PPR 预渲染。您需要为每个路由显式启用 PPR。
须知:
experimental_ppr
会应用于路由段的所有子级,包括嵌套布局和页面。无需在每个文件中添加,只需在路由的顶层段设置。- 要为子段禁用 PPR,可在子段中将
experimental_ppr
设为false
。
示例
动态 API
使用需要查看传入请求的动态 API 时,Next.js 会为该路由启用动态渲染。要继续使用 PPR,需用 Suspense 包裹组件。例如,<User />
组件因使用 cookies
API 而变为动态:
import { cookies } from 'next/headers'
export async function User() {
const session = (await cookies()).get('session')?.value
return '...'
}
import { cookies } from 'next/headers'
export async function User() {
const session = (await cookies()).get('session')?.value
return '...'
}
<User />
组件将被流式传输,而 <Page />
内的其他内容会被预渲染并成为静态外壳的一部分。
import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'
export const experimental_ppr = true
export default function Page() {
return (
<section>
<h1>这部分将被预渲染</h1>
<Suspense fallback={<AvatarSkeleton />}>
<User />
</Suspense>
</section>
)
}
import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'
export const experimental_ppr = true
export default function Page() {
return (
<section>
<h1>这部分将被预渲染</h1>
<Suspense fallback={<AvatarSkeleton />}>
<User />
</Suspense>
</section>
)
}
传递动态属性
仅当访问值时组件才会启用动态渲染。例如,若从 <Page />
组件读取 searchParams
,可将其作为属性传递给其他组件:
import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'
export default function Page({
searchParams,
}: {
searchParams: Promise<{ sort: string }>
}) {
return (
<section>
<h1>这部分将被预渲染</h1>
<Suspense fallback={<TableSkeleton />}>
<Table searchParams={searchParams} />
</Suspense>
</section>
)
}
import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'
export default function Page({ searchParams }) {
return (
<section>
<h1>这部分将被预渲染</h1>
<Suspense fallback={<TableSkeleton />}>
<Table searchParams={searchParams} />
</Suspense>
</section>
)
}
在表格组件内部,访问 searchParams
的值会使该组件变为动态,而页面其余部分会被预渲染。
export async function Table({
searchParams,
}: {
searchParams: Promise<{ sort: string }>
}) {
const sort = (await searchParams).sort === 'true'
return '...'
}
export async function Table({ searchParams }) {
const sort = (await searchParams).sort === 'true'
return '...'
}