Partial Prerendering(2)
動的コンテンツの埋め込みによりDynamic Renderingになった挙動
それでは次に、このページをDynamic Renderingの対象にしてみましょう。通常、Dynamic Renderingの対象になる条件はcookies()
やheaders()
等の Dynamic APIを利用した場合や、fetch()
関数で明示的にキャッシュ機能をオフにした場合です。
試しに、外部APIを呼び出す動的なコンテンツを埋め込んでみましょう。リスト6は、外部APIからデータを取得して表示するコンポーネントを追加した例です。
// app/page.js async function Uuid() { const res = await fetch("https://httpbin.org/uuid", { cache: "no-store", // (1) }); const data = await res.json(); return ( <div className="p-1 border-2"> <h2>生成完了</h2> <p>UUID: {data.uuid}</p> </div> ) }
動作確認にはhttpbin.orgを使わせてもらいました。(1)で明示的にキャッシュをオプトアウトしているため、このコンポーネントを埋め込むことで、ページ全体がDynamic Renderingの対象になります。リスト7は、このコンポーネントをページに埋め込んだ例です。
// app/page.js import { Suspense } from "react"; export default async function Home() { return ( <div className="flex flex-col h-screen"> <Header /> <div className="flex flex-1"> <SideMenu /> {/* メインコンテンツ */} <main className="flex-1 p-6"> <h2 className="text-xl font-bold mb-4">メイン</h2> {/* (1) */} <Suspense fallback={<FallBack />}> <Uuid /> </Suspense> </main> </div> </div> ); } {/* (2) */} function FallBack() { return ( <div className="p-1 border-2"> <p>UUIDの生成を待っています</p> </div> ) } // 省略
(1)でUuid
コンポーネントを埋め込んでみました。画面の初期表示には間に合わなくてもいい優先度の低いコンテンツであると仮定して、 Suspense
コンポーネントを使ってローディング中の表示を行うようにしてみました。(2)でレスポンス待ちの間の表示を行うコンポーネントを定義しています。このページを動かしてみると、図5のような待ち画面が表示された後、図6のようにUUIDが表示されます。
さて、ビルド結果はどうなったでしょうか。npm run build
を行うと、リスト8のような結果が得られます。
$ npm run build # 省略 Route (app) Size First Load JS ┌ ƒ / 139 B 100 kB ├ ○ /_not-found 896 B 101 kB └ ○ /server-actions 139 B 100 kB + First Load JS shared by all 99.9 kB ├ chunks/4bd1b696-80bcaf75e1b4285e.js 52.5 kB ├ chunks/517-d083b552e04dead1.js 45.5 kB └ other shared chunks (total) 1.88 kB ○ (Static) prerendered as static content ƒ (Dynamic) server-rendered on demand
リスト5から変わって、ルートディレクトリにf
のマークがついています。これは、Dynamic Renderingが行われるページであることを示しています。.next/server/app
フォルダを見に行くと、index.htmlやindex.rscは生成されていません(図7)。
常にページ全体を動的に生成することになるので、いらないコストがかかってしまいそうです。
Partial Prerenderingで挙動がどう変わるか
ここで、Partial Prerenderingの出番です。Uuid
コンポーネントは事前に生成しておくわけにはいきませんが、Suspenseコンポーネントのfallback
属性に指定したコンポーネントなら、事前に静的に生成しておいても問題ありませんよね。
Partial Prerenderingは、Dynamic Renderingが必要な部分が含まれていたとしても、Suspenseで囲んであればページ全体としては静的に生成できるようにするための機能です。Partial Prerenderingを有効にするには、 next.config.mjs
にexperimental.ppr
プロパティを追加します。
Next.js 15時点では、この設定はCanary版のバージョンでしか動かないことに注意してください。リスト9は、Partial Prerenderingを有効にする設定例です。
/** @type {import('next').NextConfig} */ const nextConfig = { experimental: { ppr: 'incremental', // (1) }, }; export default nextConfig;
(1)の設定で、Partial Prerenderingを有効にしています。incremental
は、事前レンダリングをオプトインで有効にしたページだけを処理対象にすることを意味しています。app/page.js
でオプトインの設定をしましょう。
// app/page.js // 省略 export const experimental_ppr = true // 省略
これで準備ができました。ビルドしてみると、リスト11のような結果が得られます。
$ npm run build # 省略 Route (app) Size First Load JS ┌ ◐ / 137 B 102 kB ├ ○ /_not-found 894 B 102 kB └ ○ /server-actions 137 B 102 kB + First Load JS shared by all 101 kB ├ chunks/308-96f8fe85dc0aea53.js 46.6 kB ├ chunks/f5e865f6-2c6e9f68666fedff.js 53 kB └ other shared chunks (total) 1.88 kB ○ (Static) prerendered as static content ◐ (Partial Prerender) prerendered as static HTML with dynamic server-streamed content
ビルド結果を見ると、ルートディレクトリにPartial Prerenderingのマークがついています。.next/server/app
フォルダを見に行くと、index.html
や index.prefetch.rsc
が生成されていることが確認できます(図8)。
これで、部分的に動的コンテンツを含むページでも、全体としては静的レンダリングの対象にすることができました。これにより、ページの表示速度を向上させることができそうです。これは正式実装が待ち遠しいですね。
まとめ
Next.js 14の目玉機能である、Server ActionsとPartical Prerenderingについて解説しました。Partial PrerenderingはSuspenseと静的レンダリングのあるべき姿という感じで、App Routerの進化が感じられましたね。
次回は、Next.js 15で追加された新機能について解説します。お楽しみに!