NextJS中文文档 - Partial Prerendering
部分预渲染(Partial Prerendering,PPR)是一种渲染策略,允许你在同一路由中结合静态和动态内容。这可以提高初始页面性能,同时仍然支持个性化的动态数据。
当用户访问一个路由时:
- 服务器发送一个包含静态内容的外壳,确保快速的初始加载。
- 外壳为动态内容留下空位,这些内容将异步加载。
- 动态空位是并行流式传输的,减少了页面的整体加载时间。
🎥 观看: 为什么需要 PPR 以及它是如何工作的 → YouTube(10 分钟)。
部分预渲染是如何工作的?
部分预渲染使用 React 的 Suspense 来延迟渲染应用程序的某些部分,直到满足某些条件。
Suspense 的回退内容会与静态内容一起嵌入到初始 HTML 中。在构建时(或重新验证期间),静态内容和回退内容会被预渲染以创建静态外壳。动态内容的渲染会推迟到用户请求该路由时。
将组件包装在 Suspense 中并不会使组件本身变成动态的,而是使用 Suspense 作为封装动态内容的边界。
jsx
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>
</>
)
}
为了避免客户端-服务器瀑布流,动态组件会与静态预渲染并行从服务器开始流式传输。这允许它们在浏览器加载客户端 JavaScript 之前开始渲染。
为了减少网络开销,PPR 在单个 HTTP 响应中发送静态和动态内容,避免了每个动态组件的额外往返。
启用部分预渲染
你可以通过在 next.config.ts
文件中添加 ppr
选项来启用 PPR:
ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig
js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
ppr: 'incremental',
},
}
'incremental'
值允许你为特定路由采用 PPR:
tsx
export const experimental_ppr = true
export default function Layout({ children }: { children: React.ReactNode }) {
// ...
}
jsx
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:
jsx
import { cookies } from 'next/headers'
export async function User() {
const session = (await cookies()).get('session')?.value
return '...'
}
tsx
import { cookies } from 'next/headers'
export async function User() {
const session = (await cookies()).get('session')?.value
return '...'
}
<User />
组件将被流式传输,而 <Page />
中的任何其他内容都将被预渲染并成为静态外壳的一部分。
tsx
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>
)
}
jsx
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
,你可以将这个值作为 prop 转发给另一个组件:
tsx
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>
)
}
jsx
import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'
export default function Page({ searchParams }) {
return (
<section>
<h1>这部分将被预渲染</h1>
<Suspense fallback={<TableSkeleton />}>
<Table searchParams={searchParams} />
</Suspense>
</section>
)
}
在表格组件内部,访问 searchParams
中的值将使组件变为动态的,而页面的其余部分将被预渲染。
tsx
export async function Table({ searchParams }: { searchParams: Promise<{ sort: string }> }) {
const sort = (await searchParams).sort === 'true'
return '...'
}
jsx
export async function Table({ searchParams }) {
const sort = (await searchParams).sort === 'true'
return '...'
}