Next.js での動的インポートによるコード分割

コード分割とスマート読み込み戦略で Next.js アプリを高速化する方法。

学習内容

この投稿では、さまざまな種類のコード分割と、動的インポートを使用して Next.js アプリを高速化する方法について説明します。

ルートベースとコンポーネント ベースのコード分割

デフォルトでは、Next.js は JavaScript をルートごとに別々のチャンクに分割します。ユーザーがアプリケーションを読み込むと、Next.js は初期ルートに必要なコードのみを送信します。ユーザーがアプリケーション内を移動すると、他のルートに関連付けられたチャンクを取得します。ルートベースのコード分割により、一度に解析してコンパイルする必要があるスクリプトの量が最小限に抑えられ、ページの読み込み時間が短縮されます。

ルートベースのコード分割はデフォルトとして適切ですが、コンポーネント レベルでのコード分割を使用すると、読み込みプロセスをさらに最適化できます。アプリ内に大きなコンポーネントがある場合は、コンポーネントを別々のチャンクに分割することをおすすめします。これにより、重要ではない、または特定のユーザー操作(ボタンのクリックなど)でのみレンダリングされる大きなコンポーネントを遅延読み込みできます。

Next.js は動的 import() をサポートしています。これにより、JavaScript モジュール(React コンポーネントを含む)を動的にインポートし、各インポートを個別のチャンクとして読み込むことができます。これにより、コンポーネント レベルでコードを分割できます。また、ユーザーが表示しているサイトの一部に必要なコードのみをダウンロードできるように、リソースの読み込みを制御できます。Next.js では、これらのコンポーネントがデフォルトでサーバーサイド レンダリング(SSR)されます。

動的インポートの実例

この投稿では、1 つのボタンがあるシンプルなページで構成されるサンプルアプリのバージョンをいくつか紹介します。ボタンをクリックすると、かわいい子犬が表示されます。アプリの各バージョンを参照しながら、動的インポートと静的インポートの違いや操作方法を確認します。

アプリの最初のバージョンでは、子犬は components/Puppy.js に住んでいます。子犬をページに表示するため、アプリは静的な import ステートメントを使用して index.jsPuppy コンポーネントをインポートします。

import Puppy from "../components/Puppy";

Next.js がアプリをバンドルする仕組みを確認するには、DevTools でネットワーク トレースを調べます。

  1. サイトをプレビューするには、[アプリを表示] を押してから、全画面表示 全画面表示 を押します。

  2. Ctrl+Shift+J キー(Mac の場合は Command+Option+J キー)を押して DevTools を開きます。

  3. [Network] タブをクリックします。

  4. [キャッシュを無効にする] チェックボックスをオンにします。

  5. ページを再読み込みする。

ページを読み込むと、Puppy.js コンポーネントを含むすべての必要なコードが index.js にバンドルされています。

6 つの JavaScript ファイル(index.js、app.js、webpack.js、main.js、0.js、dll(ダイナミック リンク ライブラリ)ファイル)が表示されている DevTools の [Network] タブ。

[Click me] ボタンを押すと、子犬の JPEG のリクエストのみが [Network] タブに追加されます。

ボタンをクリックすると、DevTools の [Network] タブで、同じ 6 つの JavaScript ファイルと 1 つの画像が表示されている。

このアプローチの欠点は、ユーザーが子犬を表示するためにボタンをクリックしない場合でも、Puppy コンポーネントが index.js に含まれているため、読み込む必要があるということです。この小さな例では、それほど大きな問題ではありませんが、実際のアプリケーションでは、必要なときだけ大規模なコンポーネントを読み込むことで、多くの場合大きな改善となります。

次に、アプリの 2 番目のバージョンを確認します。ここでは、静的インポートが動的インポートに置き換えられています。Next.js には next/dynamic が含まれているため、Next のどのコンポーネントにも動的インポートを使用できます。

import Puppy from "../components/Puppy";
import dynamic from "next/dynamic";

// ...

const Puppy = dynamic(import("../components/Puppy"));

最初の例の手順に沿ってネットワーク トレースを調べます。

アプリを初めて読み込むと、index.js のみがダウンロードされます。今回は Puppy コンポーネントのコードが含まれていないため、0.5 KB 小さくなっています(37.9 KB から 37.4 KB に減少しています)。

同じ 6 つの JavaScript ファイルを表示する DevTools Network では、index.js のサイズが 0.5 KB 小さくなりました。

これで、Puppy コンポーネントは個別のチャンク 1.js に入れられました。このチャンクは、ボタンを押したときにのみ読み込まれます。

ボタンをクリックすると、DevTools の [Network] タブ。追加の 1.js ファイルと、ファイルリストの下部に追加された画像が表示されています。

実際のアプリケーションでは、コンポーネントがはるかに大きくなることが多く、遅延読み込みを行うことで、最初の JavaScript ペイロードを数百キロバイト削減できます。

カスタム読み込みインジケーターを使用した動的インポート

リソースを遅延読み込みする場合は、遅延が発生した場合に備えて読み込みインジケーターを表示することをおすすめします。Next.js では、dynamic() 関数に追加の引数を指定します。

const Puppy = dynamic(() => import("../components/Puppy"), {
  loading: () => <p>Loading...</p>
});

読み込みインジケーターの動作を確認するには、DevTools で低速なネットワーク接続をシミュレートします。

  1. サイトをプレビューするには、[アプリを表示] を押してから、全画面表示 全画面表示 を押します。

  2. Ctrl+Shift+J キー(Mac の場合は Command+Option+J キー)を押して DevTools を開きます。

  3. [Network] タブをクリックします。

  4. [キャッシュを無効にする] チェックボックスをオンにします。

  5. [Throttling] プルダウン リストで [Fast 3G] を選択します。

  6. [Click me] ボタンをクリックします。

ボタンをクリックすると、コンポーネントの読み込みにしばらく時間がかかり、アプリに「Loading...」というメッセージが表示されます。

暗い画面にテキストが表示されている

SSR を使用しない動的インポート

クライアントサイドでのみコンポーネント(チャット ウィジェットなど)をレンダリングする必要がある場合は、ssr オプションを false に設定します。

const Puppy = dynamic(() => import("../components/Puppy"), {
  ssr: false,
});

まとめ

動的インポートをサポートしている Next.js では、コンポーネント レベルのコード分割が可能です。これにより、JavaScript ペイロードを最小限に抑え、アプリケーションの読み込み時間を改善できます。デフォルトでは、すべてのコンポーネントがサーバー側でレンダリングされます。このオプションは、必要に応じていつでも無効にできます。