これはNext.jsの公式チュートリアルの10. Partial Prerendering (Optional) に関するメモです
前章のメモ
Next.jsの公式チュートリアルの該当ページ
学ぶこと
- Partial Prerendering とは何か?どう機能するのか?
静的レンダリング vs 動的レンダリング
WEBアプリではアプリ全体もしくはページ全体に対して、静的or動的 どちらのレンダリングにするか選択。
Next.jsにおいては動的関数を呼び出すとページ全体が動的レンダリングに。
ただ、アプリケーションによっては部分的に静的(もしくは動的)レンダリングにしたいときがある
例えば、ECサイトであれば
- 商品情報は人によらないので静的レンダリング
- ユーザへのオススメ商品は人によるので動的レンダリング
とか。
こういった要件を満たすにはPartial Prerendering を使っていきます
Partial Prerendering とは何か?
Next.js 14で導入された静的レンダリングと動的レンダリングを同時に使えるレンダリング。
静的レンダリングはすぐに表示され、動的レンダリングは非同期に並列でストリーミング。
先ほどの例だと、同じページ内で
- 商品情報は人によらないので静的レンダリング
- ユーザへのオススメ商品は人によるので動的レンダリング
と使い分けられる
Partial Prerenderingの実装と仕組み
Partial Prerendering(PPR)を実装する方法を見ていきましょう
まずは、next.config.js にオプションを追加します
1 2 3 4 5 6 7 8 9 10 |
/** @type {import('next').NextConfig} */ const nextConfig = { experimental: { // true: すべてのページでPPRが使える // incremental: 特定のページでPPRが使える ppr: 'incremental', }, }; module.exports = nextConfig; |
今回は ppr: ‘incremental’ としたので使いたいページで experimental_ppr を true にします
ダッシュボード画面に適用させたいので、/app/dashboard/layout.tsx へ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import SideNav from '@/app/ui/dashboard/sidenav'; export const experimental_ppr = true; export default function Layout({ children }: { children: React.ReactNode }) { return ( <div className="flex h-screen flex-col md:flex-row md:overflow-hidden"> <div className="w-full flex-none md:w-64"> <SideNav /> </div> <div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div> </div> ); } |
これでPPRを実装できました。
既存のコードを変えることなく、オプションや変数を少し追加するだけでPPRは使えます。
PPRを使用しているページでは
- Suspenseで囲まれている ⇒ 動的レンダリング
- それ以外 ⇒ 静的レンダリング
になります。つまり、Suspenseを基準にNext.jsが勝手に静的 or 動的を判断してくれます
ただ、これはアプリをビルドする際に見ることができます
早速、ビルドしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
nextjs-dashboard$ npm run build > build > next build ▲ Next.js 15.0.0-canary.58 - Environments: .env - Experiments (use with caution): · ppr Creating an optimized production build ... ✓ Compiled successfully ✓ Linting and checking validity of types ✓ Collecting page data ✓ Generating static pages (7/7) ✓ Collecting build traces ✓ Finalizing page optimization Route (app) Size First Load JS ┌ ○ / 229 B 104 kB ├ ○ /_not-found 895 B 91.3 kB ├ ◐ /dashboard 302 B 95.8 kB ├ ○ /dashboard/customers 142 B 90.5 kB └ ○ /dashboard/invoices 142 B 90.5 kB + First Load JS shared by all 90.4 kB ├ chunks/440-9e545f86aac8f1b5.js 36.4 kB ├ chunks/f5e865f6-62384e348f14a4cc.js 52.1 kB └ other shared chunks (total) 1.92 kB ○ (Static) prerendered as static content ◐ (Partial Prerender) prerendered as static HTML with dynamic server-streamed content |
/dashboard がPartial Prerenderと判断されています
ちなみに、こちらがPPRを使用していないときのビルドログ。
/dashboard が動的レンダリングと判断されています
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
nextjs-dashboard$ npm run build > build > next build ▲ Next.js 14.0.2 - Environments: .env Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest Why you should do it regularly: https://github.com/browserslist/update-db#readme Browserslist: caniuse-lite is outdated. Please run: npx browserslist@latest --update-db Why you should do it regularly: https://github.com/browserslist/browserslist#browsers-data-updating ✓ Creating an optimized production build ✓ Compiled successfully ✓ Linting and checking validity of types ✓ Collecting page data ✓ Generating static pages (7/7) ✓ Collecting build traces ✓ Finalizing page optimization Route (app) Size First Load JS ┌ ○ / 226 B 96.4 kB ├ ○ /_not-found 876 B 85.3 kB ├ λ /dashboard 298 B 89.7 kB ├ ○ /dashboard/customers 144 B 84.6 kB └ ○ /dashboard/invoices 145 B 84.6 kB + First Load JS shared by all 84.4 kB ├ chunks/472-d678cdfa8ebba6a0.js 29.2 kB ├ chunks/fd9d1056-ad47410d9999f966.js 53.3 kB ├ chunks/main-app-e60c0ab0c0326f20.js 219 B └ chunks/webpack-1c09ca629753d40f.js 1.71 kB ○ (Static) prerendered as static content λ (Dynamic) server-rendered on demand using Node.js |
PPRの実装部をコメントアウトしておく
Next.jsのCanaryバージョン出ないとビルドできないため、
PPRに関するコードをコメントアウトしておきます
1 2 3 4 5 6 7 8 9 10 |
/** @type {import('next').NextConfig} */ const nextConfig = { // experimental: { // // true: すべてのページでPPRが使える // // incremental: 特定のページでPPRが使える // ppr: 'incremental', // }, }; module.exports = nextConfig; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import SideNav from '@/app/ui/dashboard/sidenav'; // export const experimental_ppr = true; export default function Layout({ children }: { children: React.ReactNode }) { return ( <div className="flex h-screen flex-col md:flex-row md:overflow-hidden"> <div className="w-full flex-none md:w-64"> <SideNav /> </div> <div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div> </div> ); } |
まとめ
ここまで、データの取得を最適化する方法としていろいろ見てきました
- レイテンシを削減するためにサーバーとデータベース間を同じリージョンに作成。
- React Server Components を使用してサーバー上でデータを取得することでクライアント側の負荷を減らす+機密情報がクライアントに公開されない
- SQL を使用して必要なデータのみを取得。
- データ取得を並列で処理する
- ストリーミングを実装することですべてが読み込まれるのを待たずに操作できる
- ページ内で静的 or 動的レンダリングを使い分ける
次章のメモ
コメント