实验功能
此功能目前处于实验阶段,可能会有所变更,不建议用于生产环境
部分预渲染 (PPR) 是一种渲染策略,允许您在同一路由中合并静态内容和动态内容。这可以提升初始页面的性能,同时仍然支持个性化的动态数据
当用户访问路由时:
- 服务端发送包含静态内容的“外壳”,确保快速的初始加载
- 该“外壳”为异步加载的动态内容留下了“孔”
- 动态“孔”并行流式传输 ,减少了页面的整体加载时间
🎥 观看: 为什么选择 PPR 以及它的工作原理 → YouTube(10 分钟)
部分预渲染如何工作?
为了理解部分预渲染,熟悉 Next.js 中可用的渲染策略会有所帮助
静态渲染
使用静态渲染,HTML 会提前生成——无论是在构建时还是通过增量静态再生,结果会被缓存并在用户和请求之间共享
在部分预渲染中,Next.js 会预渲染路由的静态“外壳”,这可以包括布局以及任何其他不依赖于请求时数据的组件
动态渲染
使用动态渲染,HTML 会在请求时生成。这允许您根据请求时数据提供个性化内容
如果组件使用以下 API,则它将成为动态的:
cookies
headers
connection
draftMode
searchParams
propunstable_noStore
fetch
with{ cache: 'no-store' }
在部分预渲染中,使用这些 API 会抛出一个特殊的 React 错误,告知 Next.js 该组件无法静态渲染,从而导致构建错误。您可以使用 Suspense 边界来包装组件,将渲染推迟到运行时
Suspense
React Suspense 用于推迟渲染应用程序的各个部分,直到满足某些条件
在部分预渲染中,Suspense 用于标记组件树中的动态边界
在构建时,Next.js 会预渲染静态内容和 fallback
UI。动态内容则会推迟到用户请求路由时才渲染
将组件包装在 Suspense 中不会使组件本身变得动态(您的 API 使用会),而是将 Suspense 用作封装动态内容并启用流式传输的边界
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>
</>
)
}
流式传输
流式传输将路由拆分成多个块,并在块准备就绪后逐步将其流式传输到客户端。这允许用户在整个内容渲染完成之前立即看到页面的某些部分
在部分预渲染中,包裹在 Suspense 中的动态组件开始从服务端并行流式传输
为了减少网络开销,完整的响应(包括静态 HTML 和流式动态部分)将通过单个 HTTP 请求发送。这避免了额外的往返,并提高了初始加载和整体性能
启用部分预渲染
您可以通过添加 ppr
选项到你的 next.config.ts
文件中来启用 PPR:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig
'incremental'
值允许您对特定路由采用 PPR:
// /app/dashboard/layout.tsx
export const experimental_ppr = true
export default function Layout({ children }: { children: React.ReactNode }) {
// ...
}
未设置 experimental_ppr
的路由将默认为 false
,并且不会使用 PPR 进行预渲染。您需要为每个路由明确启用 PPR
温馨提示
experimental_ppr
将应用于路由段的所有子段,包括嵌套布局和页面。您无需将其添加到每个文件,只需将其添加到路由的顶部段即可要禁用子段的 PPR,您可以在子段中将
experimental_ppr
设置为false
示例
动态 APIs
当使用需要查看传入请求的动态 API 时,Next.js 会选择对路由进行动态渲染。要继续使用 PPR,请使用 Suspense 包装组件。例如, <User />
组件是动态的,因为它使用了 cookies
API:
// app/user.tsx
import { cookies } from 'next/headers'
export async function User() {
const session = (await cookies()).get('session')?.value
return '...'
}
<User />
组件将被流式传输,而 <Page />
内的任何其他内容将被预渲染并成为静态“外壳”的一部分
// 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>
)
}
传递动态 props
组件仅在访问值时才会选择动态渲染。例如,如果您正在从 <Page />
组件读取 searchParams
,则可以将此值作为 prop 转发给另一个组件:
// app/page.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
的值将使组件动态化,而页面的其余部分将被预渲染
export async function Table({
searchParams,
}: {
searchParams: Promise<{ sort: string }>
}) {
const sort = (await searchParams).sort === 'true'
return '...'
}