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

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

公開日: 2019 年 11 月 8 日

さまざまな種類のコード分割と、動的インポートを使用して 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. `Control+Shift+J`(Mac の場合は `Command+Option+J`)を押して、デベロッパー ツールを開きます。

  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 に減少)。

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. `Control+Shift+J`(Mac の場合は `Command+Option+J`)を押して、デベロッパー ツールを開きます。

  3. [ネットワーク] タブをクリックします。

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

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

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

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

テキストが表示された暗い画面

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

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

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

まとめ

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