【10章】Next.jsのチュートリアルをやってみた
Partial Prerendering は Next.js 14 で追加された新しい機能です 動作が安定するまで、本番環境では慎重に使用してください
前章のメモ
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にオプションを追加します
/**@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 へ。
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 動的を判断してくれます
ただ、これはアプリをビルドする際に見ることができます
早速、ビルドしてみましょう。
ビルド時に Error: The experimental.ppr preview feature can only be enabled when using the latest canary version of Next.js. とエラーが表示された方は、
↓ の記事を参考にしてみてください!
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 が動的レンダリングと判断されています
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 に関するコードをコメントアウトしておきます
/**@type {import('next').NextConfig} */
const nextConfig = {
// experimental: {
// // true: すべてのページでPPRが使える
// // incremental: 特定のページでPPRが使える
// ppr: 'incremental',
// },
};
module.exports = nextConfig;
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 動的レンダリングを使い分ける
次章のメモ
Discussion