部分プリレンダリング (Partial Prerendering) の使用方法
部分プリレンダリング (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リクエストで送信されます。これにより、追加の往復通信を回避し、初期読み込みと全体的なパフォーマンスが向上します。
部分プリレンダリングの有効化
next.config.ts
ファイルにppr
オプションを追加することでPPRを有効にできます:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
ppr: 'incremental',
},
}
'incremental'
値を指定すると、特定のルートに対してPPRを採用できます:
export const experimental_ppr = true
export default function Layout({ children }: { children: React.ReactNode }) {
// ...
}
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を使用しているため動的です:
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 '...'
}
<User />
コンポーネントはストリーミングされ、<Page />
内の他のコンテンツは事前レンダリングされて静的シェルの一部になります。
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>
)
}
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
を読み取る場合、この値を別のコンポーネントにプロパティとして転送できます:
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>
)
}
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
から値にアクセスすると、コンポーネントが動的になり、ページの他の部分は事前レンダリングされます。
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 '...'
}