🚀

【10章】Next.jsのチュートリアルをやってみた

2025/01/12に公開

Partial Prerendering は Next.js 14 で追加された新しい機能です 動作が安定するまで、本番環境では慎重に使用してください

前章のメモ

https://zenn.dev/kuuki/articles/nextjs-tutorial-09/

Next.js の公式チュートリアルの該当ページ

https://nextjs.org/learn/dashboard-app/partial-prerendering

学ぶこと

  • Partial Prerendering とは何か?どう機能するのか?

静的レンダリング vs 動的レンダリング

WEB アプリではアプリ全体もしくはページ全体に対して、静的 or 動的 どちらのレンダリングにするか選択。

Next.js においては動的関数を呼び出すとページ全体が動的レンダリングに。

ただ、アプリケーションによっては部分的に静的(もしくは動的)レンダリングにしたいときがある

例えば、EC サイトであれば

  • 商品情報は人によらないので静的レンダリング
  • ユーザへのオススメ商品は人によるので動的レンダリング

とか。

こういった要件を満たすにはPartial Prerenderingを使っていきます

Partial Prerendering とは何か?

Next.js 14 で導入された静的レンダリングと動的レンダリングを同時に使えるレンダリング。

静的レンダリングはすぐに表示され、動的レンダリングは非同期に並列でストリーミング

先ほどの例だと、同じページ内で

  • 商品情報は人によらないので静的レンダリング
  • ユーザへのオススメ商品は人によるので動的レンダリング

と使い分けられる

Partial Prerendering の実装と仕組み

Partial Prerendering(PPR) を実装する方法を見ていきましょう

まずは、next.config.jsにオプションを追加します

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 へ。

/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. とエラーが表示された方は、
↓ の記事を参考にしてみてください!
https://zenn.dev/kuuki/articles/next-js-partial-prerendering-ppr-cant-build/

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 に関するコードをコメントアウトしておきます

next.config.js
/**@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>
  );
}

まとめ

ここまで、データの取得を最適化する方法としていろいろ見てきました

  1. レイテンシを削減するためにサーバーとデータベース間を同じリージョンに作成。
  2. React Server Components を使用してサーバー上でデータを取得することでクライアント側の負荷を減らす+機密情報がクライアントに公開されない
  3. SQL を使用して必要なデータのみを取得。
  4. データ取得を並列で処理する
  5. ストリーミングを実装することですべてが読み込まれるのを待たずに操作できる
  6. ページ内で静的 or 動的レンダリングを使い分ける

次章のメモ

https://zenn.dev/kuuki/articles/nextjs-tutorial-11/

Discussion