Skip to content

部分预渲染

¥Partial Prerendering

部分预渲染 (PPR) 是一种渲染策略,允许你在同一路由中组合静态和动态内容。这可以提升初始页面性能,同时仍然支持个性化的动态数据。

¥Partial Prerendering (PPR) is a rendering strategy that allows you to combine static and dynamic content in the same route. This improves the initial page performance while still supporting personalized, dynamic data.

当用户访问路由时:

¥When a user visits a route:

  • 服务器发送一个包含静态内容的 shell,以确保快速的初始加载。

    ¥The server sends a shell containing the static content, ensuring a fast initial load.

  • 该 shell 为异步加载的动态内容留出了空隙。

    ¥The shell leaves holes for the dynamic content that will load in asynchronously.

  • 动态内容并行传输,从而减少了页面的整体加载时间。

    ¥The dynamic holes are streamed in parallel, reducing the overall load time of the page.

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

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

部分预渲染如何工作?

¥How does Partial Prerendering work?

为了理解部分预渲染,熟悉 Next.js 中可用的渲染策略会有所帮助。

¥To understand Partial Prerendering, it helps to be familiar with the rendering strategies available in Next.js.

静态渲染

¥Static Rendering

使用静态渲染时,HTML 会提前生成 - 无论是在构建时还是通过 revalidation。结果将被缓存并在用户和请求之间共享。

¥With Static Rendering, HTML is generated ahead of time—either at build time or through revalidation. The result is cached and shared across users and requests.

在部分预渲染中,Next.js 会为路由预渲染一个静态 shell。这可能包括布局和任何其他不依赖于请求时间数据的组件。

¥In Partial Prerendering, Next.js prerenders a static shell for a route. This can include the layout and any other components that don't depend on request-time data.

动态渲染

¥Dynamic Rendering

使用动态渲染时,HTML 会在请求时生成。这允许你根据请求时间数据提供个性化内容。

¥With Dynamic Rendering, HTML is generated at request time. This allows you to serve personalized content based on request-time data.

如果组件使用以下 API,则该组件将变为动态的:

¥A component becomes dynamic if it uses the following APIs:

在部分预渲染中,使用这些 API 会抛出一个特殊的 React 错误,通知 Next.js 该组件无法静态渲染,从而导致构建错误。你可以使用 悬念 边界包裹你的组件,以将渲染推迟到运行时。

¥In Partial Prerendering, using these APIs throws a special React error that informs Next.js the component cannot be statically rendered, causing a build error. You can use a Suspense boundary to wrap your component to defer rendering until runtime.

悬念

¥Suspense

React 悬念 用于延迟渲染应用的各个部分,直到满足某些条件为止。

¥React Suspense is used to defer rendering parts of your application until some condition is met.

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

¥In Partial Prerendering, Suspense is used to mark dynamic boundaries in your component tree.

在构建时,Next.js 会预渲染静态内容和 fallback UI。动态内容将被推迟到用户请求路由时才显示。

¥At build time, Next.js prerenders the static content and the fallback UI. The dynamic content is postponed until the user requests the route.

将组件封装在 Suspense 中并不会使其本身动态化(你的 API 使用会),而是使用 Suspense 作为封装动态内容并启用 streaming 的边界。

¥Wrapping a component in Suspense doesn't make the component itself dynamic (your API usage does), but rather Suspense is used as a boundary that encapsulates dynamic content and enable streaming

流式

¥Streaming

流式传输将路由拆分成多个块,并在它们准备就绪时逐步将它们流式传输到客户端。这允许用户在整个内容完成渲染之前立即看到页面的部分内容。

¥Streaming splits the route into chunks and progressively streams them to the client as they become ready. This allows the user to see parts of the page immediately, before the entire content has finished rendering.

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

¥In Partial Prerendering, dynamic components wrapped in Suspense start streaming from the server in parallel.

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

¥To reduce network overhead, the full response—including static HTML and streamed dynamic parts—is sent in a single HTTP request. This avoids extra roundtrips and improves both initial load and overall performance.

启用部分预渲染

¥Enabling Partial Prerendering

你可以通过在 next.config.ts 文件中添加 ppr 选项来启用 PPR:

¥You can enable PPR by adding the ppr option to your next.config.ts file:

ts
import type { NextConfig } from 'next'

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

export default nextConfig

'incremental' 值允许你为特定路由采用 PPR:

¥The 'incremental' value allows you to adopt PPR for specific routes:

tsx
export const experimental_ppr = true

export default function Layout({ children }: { children: React.ReactNode }) {
  // ...
}

没有 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.

需要了解:

¥Good to know:

  • 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.

示例

¥Examples

动态 API

¥Dynamic APIs

当使用需要查看传入请求的动态 API 时,Next.js 将选择对路由进行动态渲染。要继续使用 PPR,请使用 Suspense 封装组件。例如,<User /> 组件是动态的,因为它使用了 cookies API:

¥When using Dynamic APIs that require looking at the incoming request, Next.js will opt into dynamic rendering for the route. To continue using PPR, wrap the component with Suspense. For example, the <User /> component is dynamic because it uses the cookies API:

tsx
import { cookies } from 'next/headers'

export async function User() {
  const session = (await cookies()).get('session')?.value
  return '...'
}

<User /> 组件将被流式传输,而 <Page /> 中的任何其他内容将被预渲染并成为静态 shell 的一部分。

¥The <User /> component will be streamed while any other content inside <Page /> will be prerendered and become part of the static shell.

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

传递动态属性

¥Passing dynamic props

组件仅在访问值时选择动态渲染。例如,如果你正在从 <Page /> 组件读取 searchParams,则可以将此值作为 prop 转发给另一个组件:

¥Components only opt into dynamic rendering when the value is accessed. For example, if you are reading searchParams from a <Page /> component, you can forward this value to another component as a prop:

tsx
import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'

export default function Page({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Suspense fallback={<TableSkeleton />}>
        <Table searchParams={searchParams} />
      </Suspense>
    </section>
  )
}

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

¥Inside of the table component, accessing the value from searchParams will make the component dynamic while the rest of the page will be prerendered.

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