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

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

学習内容

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

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

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

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

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

動的インポートの動作

この投稿では、1 つのボタンがあるシンプルなページで構成されるサンプルアプリの複数のバージョンを紹介します。ボタンをクリックすると、かわいい子犬が表示されます。アプリの各バージョンを進むにつれて、動的インポートが静的インポートとどのように異なるか、また、動的インポートをどのように使用するかについて説明します。

アプリの最初のバージョンでは、子犬は components/Puppy.js に存在します。子犬をページに表示するために、アプリは静的インポート ステートメントを使用して 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 の [Network] タブ。同じ 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 に減少)。

DevTools ネットワークに、同じ 6 つの JavaScript ファイルが表示されています。ただし、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] ボタンを押します。

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

暗い画面に「

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

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

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

まとめ

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