部分プリレンダリング (Partial Prerendering) の使用方法

部分プリレンダリング (PPR) は、同じルート内で静的コンテンツと動的コンテンツを組み合わせることができるレンダリング戦略です。これにより、初期ページのパフォーマンスを向上させながら、パーソナライズされた動的データをサポートできます。

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

ユーザーがルートにアクセスすると:

  • サーバーは静的コンテンツを含むシェルを送信し、高速な初期読み込みを保証します
  • シェルには、非同期的に読み込まれる動的コンテンツのためのが残されます
  • 動的な穴は並列でストリーミングされ、ページ全体の読み込み時間を短縮します

🎥 動画で学ぶ: PPRの仕組みとその利点 → YouTube (10分)

部分プリレンダリングの仕組み

部分プリレンダリングを理解するには、Next.jsで利用可能なレンダリング戦略に慣れておくと役立ちます。

静的レンダリング

静的レンダリングでは、HTMLが事前に生成されます(ビルド時または再検証時)。結果はキャッシュされ、ユーザーやリクエスト間で共有されます。

部分プリレンダリングでは、Next.jsがルートの静的シェルを事前にレンダリングします。これには、リクエスト時のデータに依存しないレイアウトやその他のコンポーネントを含めることができます。

動的レンダリング

動的レンダリングでは、HTMLがリクエスト時に生成されます。これにより、リクエスト時のデータに基づいてパーソナライズされたコンテンツを提供できます。

以下のAPIを使用するとコンポーネントは動的になります:

部分プリレンダリングでは、これらのAPIを使用すると特別なReactエラーがスローされ、コンポーネントが静的にレンダリングできないことをNext.jsに通知し、ビルドエラーが発生します。Suspense 境界を使用してコンポーネントをラップし、ランタイムまでレンダリングを延期できます。

Suspense

ReactのSuspenseは、アプリケーションの一部のレンダリングを特定の条件が満たされるまで延期するために使用されます。

部分プリレンダリングでは、Suspenseはコンポーネントツリー内の動的境界をマークするために使用されます。

ビルド時、Next.jsは静的コンテンツとfallback UIを事前にレンダリングします。動的コンテンツは、ユーザーがルートをリクエストするまで延期されます。

コンポーネントをSuspenseでラップしても、コンポーネント自体が動的になるわけではありません(APIの使用が動的にします)。Suspenseは動的コンテンツをカプセル化し、ストリーミングを可能にする境界として使用されます。

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

ストリーミング

ストリーミングはルートをチャンクに分割し、準備が整った時点でクライアントに段階的にストリーミングします。これにより、コンテンツ全体のレンダリングが完了する前に、ユーザーはページの一部をすぐに確認できます。

クライアント側で部分的にレンダリングされたページと、ストリーミング中のチャンクのローディングUIを示す図

部分プリレンダリングでは、Suspenseでラップされた動的コンポーネントがサーバーから並列でストリーミングを開始します。

ストリーミング中のルートセグメントの並列化を示す図。個々のチャンクのデータ取得、レンダリング、ハイドレーションが表示されています

ネットワークのオーバーヘッドを削減するため、静的HTMLとストリーミングされる動的パーツを含む完全なレスポンスが単一のHTTPリクエストで送信されます。これにより、追加の往復通信を回避し、初期読み込みと全体的なパフォーマンスが向上します。

部分プリレンダリングの有効化

next.config.tsファイルにpprオプションを追加することでPPRを有効にできます:

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 }) {
  // ...
}
/app/dashboard/layout.js
export const experimental_ppr = true

export default function Layout({ children }) {
  // ...
}

experimental_pprを持たないルートはデフォルトでfalseになり、PPRを使用して事前レンダリングされません。各ルートに対して明示的にPPRをオプトインする必要があります。

知っておくと良いこと:

  • experimental_pprは、ネストされたレイアウトやページを含む、ルートセグメントのすべての子に適用されます。すべてのファイルに追加する必要はなく、ルートの最上位セグメントのみに追加します。
  • 子セグメントでPPRを無効にするには、子セグメントでexperimental_pprfalseに設定します。

動的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 '...'
}

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

動的プロパティの受け渡し

コンポーネントは、値がアクセスされたときにのみ動的レンダリングを選択します。例えば、<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>
  )
}

テーブルコンポーネント内でsearchParamsから値にアクセスすると、コンポーネントが動的になり、ページの他の部分は事前レンダリングされます。

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

On this page