如何使用部分预渲染 (Partial Prerendering)

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

部分预渲染的产品页面展示静态导航和产品信息,以及动态购物车和推荐商品

当用户访问路由时:

  • 服务器发送包含静态内容的 外壳 (shell),确保快速初始加载。
  • 外壳为动态内容预留 空洞 (holes),这些内容将异步加载。
  • 动态空洞会 并行流式传输,减少页面整体加载时间。

🎥 观看视频: PPR 原理及工作方式 → YouTube (10分钟)

部分预渲染如何工作?

理解部分预渲染前,需熟悉 Next.js 提供的渲染策略。

静态渲染

静态渲染会提前生成 HTML——在构建时或通过 重新验证 完成。结果会被缓存并在用户和请求间共享。

在部分预渲染中,Next.js 会为路由预渲染 静态外壳,可包含布局及不依赖请求时数据的组件。

动态渲染

动态渲染在 请求时 生成 HTML,允许基于请求时数据提供个性化内容。

组件在以下情况下会变为动态:

在部分预渲染中,使用这些 API 会触发特殊的 React 错误,提示 Next.js 该组件无法静态渲染,导致构建错误。您可以用 Suspense 边界包裹组件以延迟渲染至运行时。

Suspense

React 的 Suspense 用于延迟渲染部分应用直至条件满足。

在部分预渲染中,Suspense 用于标记组件树中的 动态边界

构建时,Next.js 会预渲染静态内容和 fallback UI。动态内容会 延迟 至用户请求路由时加载。

用 Suspense 包裹组件不会使组件本身变为动态(API 使用决定动态性),而是作为封装动态内容并启用 流式传输 的边界。

app/page.js
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>
    </>
  )
}

流式传输

流式传输将路由拆分为多个块,并在准备就绪时逐步流式传输至客户端。这让用户能在全部内容渲染完成前立即看到部分页面。

客户端部分渲染页面示意图,显示正在流式传输的区块加载 UI

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

流式传输期间路由段的并行化示意图,展示各区块的数据获取、渲染和水合

为减少网络开销,完整响应(包括静态 HTML 和流式动态部分)通过 单次 HTTP 请求 发送。这避免了额外往返,提升了初始加载和整体性能。

启用部分预渲染

通过将 ppr 选项加入 next.config.ts 文件启用 PPR:

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}

export default nextConfig

'incremental' 值允许您为特定路由启用 PPR:

/app/dashboard/layout.tsx
export const experimental_ppr = true

export default function Layout({ children }: { children: React.ReactNode }) {
  // ...
}
/app/dashboard/layout.js
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 '...'
}

<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>
  )
}

传递动态属性

仅当访问值时组件才会启用动态渲染。例如,若从 <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>
  )
}

在表格组件内部,访问 searchParams 的值会使该组件变为动态,而页面其余部分会被预渲染。

export async function Table({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  const sort = (await searchParams).sort === 'true'
  return '...'
}

On this page