Skip to main content

部分预渲染

注意:部分预渲染是仅在 Canary 上可用的实验性功能,可能会发生变化。它还未准备好用于生产。

¥Note: Partial Prerendering is an experimental feature only available on canary and is subject to change. It is not ready for production use.

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

¥Partial Prerendering (PPR) enables you to combine static and dynamic components together in the same route.

在构建期间,Next.js 会尽可能多地预渲染路由。如果检测到 dynamic 代码,例如从传入请求中读取,则可以用 React Suspense 边界封装相关组件。Suspense 边界回退将包含在预渲染的 HTML 中。

¥During the build, Next.js prerenders as much of the route as possible. If dynamic code is detected, like reading from the incoming request, you can wrap the relevant component with a React Suspense boundary. The Suspense boundary fallback will then be included in the prerendered HTML.

🎥 监视:为什么使用 PPR 以及它的工作原理 → YouTube(10 分钟)

¥🎥 Watch: Why PPR and how it works → YouTube (10 minutes).

背景

¥Background

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

¥PPR enables your Next.js server to immediately send prerendered content.

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

¥To prevent client to server waterfalls, dynamic components begin streaming from the server in parallel while serving the initial prerender. This ensures dynamic components can begin rendering before client JavaScript has been loaded in the browser.

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

¥To prevent creating many HTTP requests for each dynamic component, PPR is able to combine the static prerender and dynamic components together into a single HTTP request. This ensures there are not multiple network roundtrips needed for each dynamic component.

使用部分预渲染

¥Using Partial Prerendering

增量采用(版本 15)

¥Incremental Adoption (Version 15)

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

¥In Next.js 15, you can incrementally adopt Partial Prerendering in layouts and pages by setting the ppr option in next.config.js to incremental, and exporting the experimental_ppr route config option at the top of the file:

import type { NextConfig } from 'next'

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

export default nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
ppr: 'incremental',
},
}

module.exports = nextConfig
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>
</>
};
}
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>
</>
};
}

很高兴知道:

¥Good to know:

  • 没有 experimental_ppr 的路由将默认为 false,并且不会使用 PPR 进行预渲染。你需要为每条路由明确选择加入 PPR。

    ¥Routes that don't have experimental_ppr will default to false and will not be prerendered using PPR. You need to explicitly opt-in to PPR for each route.

  • experimental_ppr 将应用于路由段的所有子项,包括嵌套布局和页面。你不必将其添加到每个文件中,只需添加到路由的顶部部分即可。

    ¥experimental_ppr will apply to all children of the route segment, including nested layouts and pages. You don't have to add it to every file, only the top segment of a route.

  • 要禁用子段的 PPR,你可以在子段中将 experimental_ppr 设置为 false

    ¥To disable PPR for children segments, you can set experimental_ppr to false in the child segment.

动态组件

¥Dynamic Components

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

¥When creating the prerender for your route during next build, Next.js requires that Dynamic APIs are wrapped with React Suspense. The fallback is then included in the prerender.

例如,使用 cookiesheaders 等函数:

¥For example, using functions like cookies or headers:

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 '...'
}

此组件需要查看传入的请求以读取 cookie。要将其与 PPR 一起使用,你应该使用 Suspense 封装组件:

¥This component requires looking at the incoming request to read cookies. To use this with PPR, you should wrap the component with Suspense:

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

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

¥Components only opt into dynamic rendering when the value is accessed.

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

¥For example, if you are reading searchParams from a page, you can forward this value to another component as a prop:

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>
)
}
import { Table } from './table'

export default function Page({ searchParams }) {
return (
<section>
<h1>This will be prerendered</h1>
<Table searchParams={searchParams} />
</section>
)
}

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

¥Inside of the table component, accessing the value from searchParams will make the component run dynamically:

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 '...'
}