きめ細かいチャンクによる、Next.js と Gatsby のページ読み込みパフォーマンスの向上

Next.js と Gatsby の新しい webpack チャンキング戦略では、コードの重複を最小限に抑えて、ページ読み込みのパフォーマンスを向上させます。

Chrome はツールやサービスとコラボレーションしている 数々のフレームワークを提供しています。最近、いくつかの新しい最適化が実施されました Next.jsギャツビー。この記事では、改善されたきめ細かなチャンク戦略について説明します。 両方のフレームワークで デフォルトで出荷されるようになりました

はじめに

多くのウェブ フレームワークと同様に、Next.js と Gatsby は webpack をコアとして使用しています。 Bundler です。webpack v3 を導入 CommonsChunkPlugin にすると、 単一の(または少数の)「コモン」内の異なるエントリ ポイント間で共有される出力モジュール。チャンク(または 分割されます。共有コードは個別にダウンロードしてブラウザのキャッシュに保存でき、 読み込みのパフォーマンスが向上します

このパターンは、多くのシングルページ アプリケーション フレームワークでエントリポイントと バンドル構成は次のようになります。

共通のエントリ ポイントとバンドル構成

すべての共有モジュール コードを 1 つのチャンクにまとめるというコンセプトは実用的ですが、 あります。すべてのエントリ ポイントで共有されていないモジュールは、このモジュールを使用しないルート用にダウンロードできる 必要以上に多くのコードがダウンロードされますたとえば、page1 が読み込まれたときなどです。 page1moduleC を使用していない場合でも、moduleC のコードを読み込みます。common このため、Webpack v4 では、他のいくつかのプラグインとともに、新しい 1: SplitChunksPlugin

チャンキングの改善

ほとんどの場合、SplitChunksPlugin はデフォルトの設定で問題ありません。分割された複数のチャンクは 複数の条件に応じて作成される コードが重複して複数のルートで 取得されないようにすることができます

ただし、このプラグインを使用する多くのウェブ フレームワークは、依然として「シングルコモン」に従っています。分割する方法を 分割しますたとえば、Next.js は commons バンドルを生成します。このバンドルには、 50% を超えるページとすべてのフレームワーク依存関係(reactreact-dom など)で使用されている。

const splitChunksConfigs = {
  …
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

フレームワーク依存のコードを共有チャンクに含めると、そのコードをダウンロードして、 共通モジュールを含めるという使用状況に基づくヒューリスティックが、 ページの半分では効果は得られません。この比率を変更しても、結果は次のいずれかになります。

  • この比率を減らすと、より多くの不要なコードがダウンロードされます。
  • 比率を増やすと、複数のルート間で重複するコードが増加します。

この問題を解決するために、Next.js は別の手法を採用しました。 負荷を軽減する SplitChunksPlugin 用の構成です。 必要はありません。

  • 十分な大きさのサードパーティ モジュール(160 KB 超)は、それぞれ独立したモジュールに分割されます。 チャンク
  • フレームワークの依存関係(reactreact-domframeworks など)
  • 必要な数の共有チャンクが作成されます(最大 25 個)
  • 生成されるチャンクの最小サイズが 20 KB に変更される

このきめ細かなチャンク戦略には、次の利点があります。

  • ページの読み込み時間が短縮されます。1 つのチャンクではなく複数の共有チャンクを出力することで、 エントリポイントで不要な(または重複する)コードの量を最小限に抑えます。
  • 移動中のキャッシュ保存の改善。大規模なライブラリとフレームワークの依存関係の分割 分割することで、キャッシュが無効化される可能性が低くなります。キャッシュが無効化される可能性は、 変更されることはありません。

Next.js が採用した構成の全体は webpack-config.ts で確認できます。

その他の HTTP リクエスト

SplitChunksPlugin では、きめ細かなチャンクの基盤を定義し、このアプローチを まったく新しいコンセプトではありません。しかし、多くのフレームワークは依然として 単一のヒューリスティックな「コモン」を使用して一括販売戦略にはいくつかの理由がありますこれには サイトのパフォーマンスに悪影響を及ぼす可能性があります。

ブラウザが 1 つのオリジンに対して開く TCP 接続の数には上限があるため(Chrome の場合は 6 つ) バンドラから出力されるチャンク数を最小限に抑えると、リクエストの合計数を このしきい値未満にとどまる場合がありますただし、これは HTTP/1.1 にのみ当てはまります。HTTP/2 での多重化 単一の接続を使用して、複数のリクエストを並行してストリーミングできます。 含まれます。つまり、通常はチャンク数の制限について心配する必要が 出力する必要があります

すべての主要なブラウザで HTTP/2 がサポートされています。Chrome チームと Next.js チーム Next.js の 1 つの「コモン」を分割してリクエスト数を増やすかどうかを確認したいと考えました。バンドル 複数の共有チャンクに分割すると、読み込みのパフォーマンスになんらかの影響を及ぼします。同社はまず 並列リクエストの最大数を変更しながら、単一サイトのパフォーマンスを maxInitialRequests プロパティです。

リクエスト数の増加によるページ読み込みのパフォーマンス

1 つのウェブページで試行が平均 3 回実行されると、 load start-render 最大初期値を変えても、First Contentful Paint 時間はすべてほぼ同じでした。 (5 ~ 15)。興味深いことに、パフォーマンス上のオーバーヘッドは 数百のリクエストに積極的に分割した後、

数百のリクエストに対するページ読み込みのパフォーマンス

この結果から、信頼性のしきい値(20 ~ 25 件のリクエスト)以下にとどまることで、最適なバランスを実現できることがわかりました。 パフォーマンスの違いに 大きく影響しますある程度のベースライン テストを実施した結果、25 個が maxInitialRequest 数。

並列で実行されるリクエストの最大数を変更すると、複数のリクエストが 共有バンドルを作成し、エントリ ポイントごとに適切に分離することで、 不要なコードの量を減らすことができます。

チャンクの増加による JavaScript ペイロードの削減

このテストでは、リクエストの数を変更して、 パフォーマンスに悪影響を及ぼします結果から、maxInitialRequests を テストページの 25 は、速度を低下させることなく JavaScript のペイロード サイズを削減できるため、最適でした 下にありますページのハイドレートに必要な JavaScript の総量がまだ残っている これはページ読み込みのパフォーマンスが 必要ありません。

webpack は、生成されるチャンクのデフォルトの最小サイズとして 30 KB を使用します。ただし、 maxInitialRequests の値を 25、最小サイズを 20 KB にすると、キャッシュ保存が改善されました。

きめ細かなチャンクによるサイズ削減

Next.js を含む多くのフレームワークは、クライアントサイドのルーティング(JavaScript によって処理)を利用して、 新しいスクリプトタグを使用します。しかし、これらの動的チャンクをビルド時にどのように事前決定するのでしょうか。

Next.js は、サーバーサイドのビルド マニフェスト ファイルを使用して、どの出力チャンクを使用するかを 異なるエントリ ポイントになります。この情報をクライアントにも提供するため、 すべてのエントリ ポイントのすべての依存関係をマッピングするために、ビルド マニフェスト ファイルが作成されました。

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Next.js アプリケーションでの複数の共有チャンクの出力。

この新しいきめ細かなチャンク戦略は、まず Next.js でフラグの設定によりロールアウトされ、 先行ユーザーの人数ですその多くは、キャンペーンに使用される JavaScript の合計が大幅に減少しました。 サイト全体:

で確認できます。
ウェブサイト JS の合計変更数 違い(%)
https://www.barnebys.com/ -238 KB -23%
https://sumup.com/ -220 KB -30%
https://www.hashicorp.com/ -11 MB -71%
JavaScript のサイズ削減 - すべてのルート(圧縮)

最終バージョンはデフォルトでバージョン 9.2 でリリースされました。

ギャツビー

Gatsby では、使用状況に基づいて ヒューリスティックを使用して、共通モジュールを定義することができます。

config.optimization = {
  …
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

また、同様の詳細なチャンク戦略を採用するように webpack の構成を最適化することで、 多くの大規模サイトで JavaScript が大幅に削減されています。

で確認できます。
ウェブサイト JS の合計変更数 違い(%)
https://www.gatsbyjs.org/ ~ 680 KB -22%
https://www.thirdandgrove.com/ -390 KB -25%
https://ghost.org/ -1.1 MB -35%
https://reactjs.org/ -80KB -8%
JavaScript のサイズ削減 - すべてのルート(圧縮)

PR を確認し、彼らがどのように活動しているかを把握します。 このロジックは、v2.20.7 でデフォルトで提供される webpack 構成に実装されています。

まとめ

きめ細かなチャンクを配信するという考え方は、Next.js や Gatsby、さらには webpack に固有のものではありません。全ユーザー対象 大きな「コモンズ」に従っている場合、アプリケーションのチャンキング戦略の改善を検討すべきです。 使用するフレームワークやモジュール バンドラに関係なく、バンドル アプローチを採用しています。

  • 通常の React アプリケーションでも同じチャンク最適化が適用されるかどうかを確認する場合は、 こちらのサンプルの アプリ。使用される きめ細かなチャンク戦略の簡易版です。 サイトにロジックを追加します
  • ロールアップの場合、デフォルトでチャンクは細かく作成されます。注目すべきデータ 手動で設定する場合は manualChunks 動作を設定します。