YOSHIMO
homegithubtwitter

Partial Prerendering

PPR というレンダリングモデル。

yoshimo
23 February, 2025

はじめに

みなさんお久しぶりです!Web エンジニアの yoshimo(nakamoto)です。

今回は Next.js が提唱する Partial Prerendering(以下 PPR)という新たなレンダリングモデルを共有します。まず PPR は現在も開発中の機能で Next.js 15 からこちらに沿って試験的に利用できます。

https://nextjs.org/docs/app/api-reference/config/next-config-js/ppr

Pages Router 時代

PPR が登場する以前の Next.js(Pages Router 時代)のレンダリングモデルには SSR, SSG, ISR の3つがありました。SSR はその名の通りにサーバーサイドでレンダリングされますが、ランタイム時のレンダリングになります。ユーザーがアプリを見てリクエストが行われたときに、サーバーサイドでレンダリングされます。

SSG は幅広い定義だと SSR の一種ですが、ランタイム時でなく、ビルド時になります。環境を用意してデプロイするとき(ビルド時にもう)サーバーサイドでレンダリングされます。

ISR は SSG の一種で SSG だとビルド時にのみレンダリングされるため、後からそのページのデータを更新したくても SSG だとできません。それを30秒ごと・10秒ごと・ユーザーがなにかをクリックしたらなどのタイミングでデータを更新するというのが ISR です。

App Router 時代

Next.js App Router が登場以降、App Router は SSR, SSG, ISR にあたる機能をサポートしてますが、その用語は使用されず、static rendering と dynamic rendering という2つの概念を使って機能を説明してます。

static rendering は SSG, ISR に相当し、build 時や revalidate 実行後にレンダリングされます。SSG も ISR も同じようなものなので static rendering(静的なレンダリング)に括っております。ただ revalidate という設定があり revalidate なしだと SSG(ビルド時にレンダリングするのみ)、revalidate ありだと ISR(何かしらのタイミングでデータを更新する)になります。dynamic rendering は従来の SSR に相当し、リクエストがあったらサーバーサイドでレンダリングされます。

Streaming SSR

さらに App Router が登場し、SSR の中でも技術的な進化がありました。現在 App Router の SSR は Streaming SSR をサポートしてますが、そもそも従来の SSR というのはまずユーザーがリクエストを行ったら、サーバーサイドで DB からデータフェッチし、そのデータをもとに HTML をブラウザに送ります。そのときに HTML のみならず CSS とか Script も一緒にブラウザに送ったら、ブラウザ側でハイドレーションという処理が行われます。

ただそれだと問題点がありました。それはデータ量が多いところがあったら、全体のレンダリングが遅延することです。例えば、ヘッダーとナビゲーションバーとメインコンテンツがあったとしたら、メインコンテンツのデータ量が多かったとき、従来の React のSSR だとメインコンテンツのデータフェッチの遅さにヘッダーとかナビゲーションも引きづられてしまいます。そのため、ユーザーが初めてページを訪れたときに真っ白なページを見ることになってしまいます。

その問題を解決するのが Streaming SSR で、Streaming SSR は取ってきたデータを順次送ります。従来の SSR だと HTML, CSS, JS, 必要なデータがすべて揃ったら一気に返しますが、Streaming SSR だとできたデータから順次返すということです。それによって一部のデータが重かったとしても既に出来てるデータからどんどん画面に表示できるので、ページを訪れたときに真っ白な画面を見ずに済みます。

それによって Google の Core Web Vitals にある TTFP(サーバーから1バイト目が返ってくるまでの時間)が改善され、FCP(最初の DOM コンテンツを描画するまでの時間)も改善されます。

補足)因みに Next.js では、Suspense を単位にチャンク分けが行われるので、React の Suspense コンポーネントは Next.js の App Router においてとても重要になります。

静的・動的データの混在

静的・動的データが混在するとき PPR 以前の App Router には2つの実装パターンがありました。

・static rendering + Client fetch:ページ自体は SSG で、クライアントで動的データを fetch する。
・Streaming SSR:静的データはキャッシュ(Data Cache)を利用して高速化しつつ、ページの一部を Suspense で遅延レンダリングにする(Next.js は色んなキャッシュがあるけど Data Cache は fetch 単位)。

ただこれらはメリット・デメリットが相反する関係にあり、状況によって最適解が異なります。それをいい感じにするのが PPR です。PPR は Streaming SSR をさらに進化させた技術です。ページ全体を static rendering としつつ、部分的に dynamic rendering を可能にします。因みに PPR で dynamic rendering させる部分を dynamic hole, async hole, hole と呼びます。

参照

https://zenn.dev/akfm/articles/nextjs-partial-pre-rendering