Menu

部分预渲染

注意: 部分预渲染是一个实验性功能,仅在 canary 版本中可用,且可能会发生变化。目前还不适合在生产环境中使用。

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

在构建过程中,Next.js 会尽可能多地预渲染路由内容。如果检测到动态代码,比如读取传入的请求,你可以用 React Suspense 边界包裹相关组件。Suspense 边界的 fallback 将被包含在预渲染的 HTML 中。

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

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

背景

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

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

为了避免为每个动态组件创建大量 HTTP 请求,PPR 能够将静态预渲染和动态组件组合到一个单一的 HTTP 请求中。这确保了不需要为每个动态组件进行多次网络往返。

使用部分预渲染

渐进式采用 (版本 15)

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

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}
 
export default nextConfig
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}
 
module.exports = nextConfig
app/page.tsx
TypeScript
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

启用 PPR (版本 14)

对于版本 14,你可以通过在 next.config.js 文件中添加 ppr 选项来启用它。这将应用于应用程序中的所有路由:

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: true,
  },
}
 
export default nextConfig
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    ppr: true,
  },
}
 
module.exports = nextConfig

动态组件

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

例如,使用 cookiesheaders 等函数:

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

这个组件需要查看传入的请求来读取 cookies。要在 PPR 中使用它,你应该用 Suspense 包裹该组件:

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

组件只有在访问值时才会选择动态渲染。

例如,如果你从一个页面中读取 searchParams,你可以将这个值作为 prop 转发给另一个组件:

app/page.tsx
TypeScript
import { Table } from './table'
 
export default function Page({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  return (
    <section>
      <h1>这部分将被预渲染</h1>
      <Table searchParams={searchParams} />
    </section>
  )
}

在表格组件内部,访问 searchParams 的值将使组件动态运行:

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