跳到内容

部分预渲染

注意: 部分预渲染是一项实验性功能,仅在 canary 版本中可用,可能会更改。它尚未准备好用于生产环境。

部分预渲染 (PPR) 使你能够将静态和动态组件组合在同一路由中。

在构建期间,Next.js 会尽可能多地预渲染路由。如果检测到动态代码,例如从传入的请求中读取,你可以使用 React Suspense boundary 包裹相关组件。Suspense boundary 的回退内容将包含在预渲染的 HTML 中。

Partially Prerendered Product Page showing static nav and product information, and dynamic cart and recommended products

🎥 观看: 为什么使用 PPR 以及它是如何工作的 → YouTube (10 分钟)

背景

PPR 使你的 Next.js 服务器能够立即发送预渲染的内容。

为了防止客户端到服务器的瀑布流,动态组件在服务初始预渲染的同时,开始从服务器并行流式传输。这确保了动态组件可以在客户端 JavaScript 加载到浏览器之前开始渲染。

为了防止为每个动态组件创建过多的 HTTP 请求,PPR 能够将静态预渲染和动态组件组合成单个 HTTP 请求。这确保了每个动态组件不需要多次网络往返。

使用部分预渲染

增量采用(版本 15 Canary 版本)

在 Next.js 15 canary 版本中,PPR 作为实验性功能提供。它尚未在稳定版本中提供。要安装

npm install next@canary

你可以在布局页面中逐步采用部分预渲染,方法是在 next.config.js 中将 ppr 选项设置为 incremental,并在文件顶部导出 experimental_ppr 路由配置选项

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}
 
export default nextConfig
app/page.tsx
import { Suspense } from 'react'
import { StaticComponent, DynamicComponent, Fallback } from '@/app/ui'
 
export const experimental_ppr = true
 
export default function Page() {
  return (
    <>
      <StaticComponent />
      <Suspense fallback={<Fallback />}>
        <DynamicComponent />
      </Suspense>
    </>
  )
}

须知:

  • 没有 experimental_ppr 的路由将默认为 false,并且不会使用 PPR 进行预渲染。你需要为每个路由显式选择启用 PPR。
  • experimental_ppr 将应用于路由段的所有子项,包括嵌套的布局和页面。你不必将其添加到每个文件,只需添加到路由的顶部段即可。
  • 要为子段禁用 PPR,你可以在子段中将 experimental_ppr 设置为 false

动态组件

next build 期间为你的路由创建预渲染时,Next.js 要求动态 API 用 React Suspense 包裹。fallback 随后将包含在预渲染中。

例如,使用 cookies 或 headers 等函数

app/user.tsx
import { cookies } from 'next/headers'
 
export async function User() {
  const session = (await cookies()).get('session')?.value
  return '...'
}

此组件需要查看传入的请求以读取 cookies。要将此与 PPR 一起使用,你应该使用 Suspense 包裹组件

app/page.tsx
import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'
 
export const experimental_ppr = true
 
export default function Page() {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Suspense fallback={<AvatarSkeleton />}>
        <User />
      </Suspense>
    </section>
  )
}

组件仅在访问值时选择加入动态渲染。

例如,如果你正在从 page 读取 searchParams,你可以将此值作为 prop 转发到另一个组件

app/page.tsx
import { Table } from './table'
 
export default function Page({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Table searchParams={searchParams} />
    </section>
  )
}

在 table 组件内部,访问 searchParams 中的值将使组件动态运行

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