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. [ネットワーク] タブをクリックします。

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

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

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

DevTools の [ネットワーク] タブに、index.js、app.js、webpack.js、main.js、0.js、dll(動的リンク ライブラリ)ファイルの 6 つの JavaScript ファイルが表示されています。

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

ボタンをクリックした後の DevTools の [ネットワーク] タブ。同じ 6 つの JavaScript ファイルと 1 つの画像が表示されています。

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

では、静的インポートが動的インポートに置き換えられたアプリの 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 ネットワーク。ただし、index.js は 0.5 KB 小さくなりました。

Puppy コンポーネントは、ボタンが押されたときにのみ読み込まれる個別のチャンク 1.js に移動しました。

ボタンのクリック後に表示された DevTools の [ネットワーク] タブ。追加された 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. [ネットワーク] タブをクリックします。

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

  5. [スロットリング] プルダウン リストで、[高速 3G] を選択します。

  6. [Click me] ボタンを押します。

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

テキスト「

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

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

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

まとめ

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