メインコンテンツまでスキップ

Partial Prerendering

注意: Partial Prerenderingは実験的な機能であり、カナリアチャンネルでのみ利用可能で、変更される可能性があります。本番環境での使用にはまだ準備が整っていません。

Partial Prerendering (PPR)を使用すると、同じルート内で静的コンポーネントと動的コンポーネントを組み合わせることができます。

ビルド中に、Next.jsはルートの可能な限り多くをプレンダーします。dynamicなコードが検出された場合(例:受信リクエストの読み取り)、関連するコンポーネントをReact Suspenseの境界でラップできます。このSuspenseの境界のフォールバックはプレンダーされたHTMLに含まれます。

静的ナビゲーションと製品情報、動的カートとおすすめ製品を表示する部分的にプレンダーされた製品ページ静的ナビゲーションと製品情報、動的カートとおすすめ製品を表示する部分的にプレンダーされた製品ページ

🎥 見る: なぜPPRなのか、そしてそれがどのように機能するのか → YouTube (10分)

背景

PPRにより、Next.jsサーバーはすぐにプレンダーされたコンテンツを送信できます。

クライアントからサーバーへのウォーターフォールを防ぐために、動的コンポーネントは初期プレンダーを提供している間にサーバーから平行してストリーミングを開始します。これにより、動的コンポーネントがクライアントのJavaScriptがブラウザにロードされる前にレンダリングを開始できるようになります。

各動的コンポーネントのために多くのHTTPリクエストを作成しないようにするために、PPRは静的プレンダーと動的コンポーネントを1つのHTTPリクエストに結合できます。これにより、各動的コンポーネントに対して複数のネットワークラウンドトリップが必要なくなります。

Partial Prerenderingの使用

インクリメンタルアダプション (バージョン15)

Next.js 15では、pprオプションをnext.config.jsincrementalに設定し、ファイルの上部でexperimental_pprroute config optionをエクスポートすることで、layoutspagesでPartial Prerenderingを段階的に採用できます;

next.config.ts
import type { NextConfig } from 'next'

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

export default nextConfig
app/page.tsx
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を使用することを明示的に選択する必要があります。
  • experimental_pprは、ネストされたlayoutsやpagesを含むルートセグメントのすべての子に適用されます。すべてのファイルに追加する必要はなく、ルートの最上部のセグメントのみに追加すればよいです。
  • 子セグメントのPPRを無効にするには、子セグメントでexperimental_pprfalseに設定できます。

動的コンポーネント

next build中にルートのプレンダーを作成する際、Next.jsはReact Suspenseで動的なAPIをラップする必要があります。そのfallbackはプレンダーに含まれます。

たとえば、cookiesheadersのような関数を使用する場合:

app/user.js
import { cookies } from 'next/headers'

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

このコンポーネントは、cookieを読み取るために受信リクエストを参照する必要があります。これをPPRで使用するには、コンポーネントをSuspenseでラップする必要があります:

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

コンポーネントは値がアクセスされたときに初めて動的レンダリングを選択します。

たとえば、pageからsearchParamsを読み取る場合、この値を別のコンポーネントにpropとして渡すことができます:

app/page.tsx
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
export async function Table({
searchParams,
}: {
searchParams: Promise<{ sort: string }>
}) {
const sort = (await searchParams).sort === 'true'
return '...'
}