Codelab: 重要なアセットをプリロードして読み込み速度を改善する

この Codelab では、いくつかのリソースをプリロードしてプリフェッチすることで、次のウェブページのパフォーマンスを向上させます。

アプリのスクリーンショット

測定

最適化を追加する前に、まずウェブサイトのパフォーマンスを測定します。

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

ライブ版の Glitch で、Lighthouse のパフォーマンス監査([Lighthouse] > [Options] > [Performance])を実行します(Lighthouse でパフォーマンスの機会を見つけるもご覧ください)。

Lighthouse では、遅れて取得されたリソースについて次の不合格の監査が表示されます。

Lighthouse: キーリクエストのプリロードの監査
  • Ctrl+Shift+J キー(Mac の場合は Command+Option+J キー)を押して DevTools を開きます。
  • [Network] タブをクリックします。
遅延検出されたリソースが表示された [Network] パネル

main.css ファイルは、HTML ドキュメントに配置された Link 要素(<link>)では取得されませんが、別の JavaScript ファイル fetch-css.jswindow.onLoad イベントの後に Link 要素を DOM に添付します。つまり、ブラウザが JS ファイルの解析と実行を終えた後にのみファイルがフェッチされます。同様に、main.css 内で指定されたウェブフォント(K2D.woff2)は、CSS ファイルのダウンロードが完了した後にのみ取得されます。

クリティカル リクエスト チェーンは、ブラウザによって優先され、取得されるリソースの順序を表します。このウェブページは現在、以下のようになっています。

├─┬ / (initial HTML file)
  └── fetch-css.js
    └── main.css
      └── K2D.woff2

CSS ファイルはリクエスト チェーンの第 3 レベルにあるため、Lighthouse では遅延検出リソースとして識別されます。

重要なリソースをプリロードする

main.css ファイルは、ページが読み込まれるとすぐに必要になる重要なアセットです。このリソースのような重要なファイルがアプリ内で取得されるものについては、リンク プリロード タグを使用し、ドキュメントの先頭に Link 要素を追加することで、リンク プリロード タグを使用してブラウザに通知します。

このアプリケーションのプリロード タグを追加します。

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
</head>

as 属性は、取得されるリソースのタイプを識別するために使用され、as="style" は、スタイルシート ファイルのプリロードに使用されます。

アプリケーションを再読み込みして、DevTools の [Network] パネルを確認します。

プリロードされたリソースが表示された [Network] パネル

CSS ファイルを取得する JavaScript の解析が完了する前に、ブラウザがどのように CSS ファイルを取得するかに注目してください。プリロードにより、ブラウザはリソースがウェブページに不可欠であるという前提のもとで、リソースのプリエンプティブ フェッチを行うことを認識します。

プリロードは、正しく使用しないと、使用されていないリソースに対して不要なリクエストを行うことにより、パフォーマンスを損なう可能性があります。このアプリでは、details.css はプロジェクトのルートにある別の CSS ファイルですが、別の /details route に使用されます。プリロードの誤った使用方法の例を示すために、このリソースのプリロードのヒントも追加します。

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
  <link rel="preload" href="details.css" as="style">
</head>

アプリケーションを再読み込みして、[Network] パネルを確認します。ウェブページで使用されていない場合でも details.css の取得リクエストが行われます。

不要なプリロードがあるネットワーク パネル

プリロードされたリソースが、読み込み後数秒以内にそのページで使用されなくなると、Chrome の [コンソール] パネルに警告が表示されます。

コンソールでのプリロード警告

この警告は、プリロード済みリソースのうち、ウェブページですぐには使用されていないものがあるかどうかを確認する際に使用します。これで、このページの不要なプリロード リンクを削除できます。

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
  <link rel="preload" href="details.css" as="style">
</head>

取得可能なすべてのタイプのリソースと、as 属性に使用する正しい値の一覧については、プリロードに関する MDN の記事をご覧ください。

将来のリソースをプリフェッチする

プリフェッチは、別のナビゲーション ルートで使用されるアセットのリクエストに使用できるブラウザヒントです。現在のページに必要な他の重要なアセットよりも優先度は低くなります。

このウェブサイトでは、画像をクリックすると個別の details/ ルートが表示されます。

詳細ルート

別の CSS ファイル details.css には、このシンプルなページに必要なスタイルがすべて含まれています。このリソースをプリフェッチするには、index.html にリンク要素を追加します。

<head>
  <!-- ... -->
  <link rel="prefetch" href="details.css">
</head>

これによってファイルのリクエストがどのようにトリガーされるかを確認するには、DevTools で [ネットワーク] パネルを開き、[キャッシュを無効にする] オプションのチェックボックスをオフにします。

Chrome DevTools でキャッシュを無効にする

アプリケーションを再読み込みし、他のすべてのファイルが取得された後に、優先度が非常に低い details.css のリクエストが行われることを確認します。

プリフェッチされたリソースを含む [Network] パネル

DevTools を開いた状態で、ウェブサイト上の画像をクリックして details ページに移動します。details.css を取得するために details.html でリンク要素が使用されるため、リソースに対するリクエストが想定どおりに行われます。

詳細ページのネットワーク リクエスト

DevTools で details.css ネットワーク リクエストをクリックして、詳細を表示します。ファイルはブラウザのディスク キャッシュから取得されます。

ディスク キャッシュから取得した詳細リクエスト

ブラウザのアイドル時間を利用して、プリフェッチは別のページに必要なリソースに対する早期リクエストを行います。これにより、ブラウザでアセットをより早くキャッシュに保存し、必要に応じてキャッシュから提供できるため、今後のナビゲーション リクエストが高速化されます。

Webpack によるプリロードとプリフェッチ

コード分割で JavaScript ペイロードを削減するの投稿では、動的インポートを使用してバンドルを複数のチャンクに分割する方法について説明しています。これは、フォームの送信時に Lodash からモジュールを動的にインポートするシンプルなアプリを使用して示します。

コード分割のデモを行う Magic Sorter アプリ

このアプリケーションの Glitch にはこちらからアクセスできます。

src/index.js, にある次のコードブロックは、ボタンがクリックされたときにメソッドを動的にインポートします。

form.addEventListener("submit", e => {
  e.preventDefault()
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

バンドルを分割すると初期サイズが小さくなるため、ページの読み込み時間を短縮できます。webpack のバージョン 4.6.0 では、動的にインポートされるチャンクのプリロードまたはプリフェッチがサポートされています。このアプリを例にすると、ブラウザのアイドル時に lodash メソッドをプリフェッチできます。ユーザーがボタンを押しても、リソースがフェッチされるまで遅延はありません。

特定のチャンクをプリフェッチするには、動的インポート内で特定の webpackPrefetch コメント パラメータを使用します。この特定のアプリケーションでは、次のように表示されます。

form.addEventListener("submit", e => {
  e.preventDefault()
  import(/* webpackPrefetch: true */ 'lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

アプリケーションが再読み込みされると、webpack はリソースのプリフェッチ タグをドキュメントのヘッダーに挿入します。これは DevTools の [要素] パネルで確認できます。

プリフェッチ タグが表示された [要素] パネル

[ネットワーク] パネルでリクエストを観察すると、他のすべてのリソースがリクエストされた後、このチャンクが低優先度で取得されていることもわかります。

プリフェッチされたリクエストが表示されている [Network] パネル

このユースケースではプリフェッチのほうが理にかなっていますが、webpack は動的にインポートされるチャンクのプリロードもサポートしています。

import(/* webpackPreload: true */ 'module')

まとめ

この Codelab では、特定のアセットのプリロードまたはプリフェッチによってサイトのユーザー エクスペリエンスがどのように向上するかについて、しっかりと理解できたはずです。これらの手法はすべてのリソースに使用するべきではなく、誤って使用するとパフォーマンスに悪影響を及ぼす可能性があることに留意することが重要です。プリロードまたはプリフェッチのみを選択することで、最良の結果が得られます。

まとめ

  • 後で検出されたものの、現在のページにとって不可欠なリソースには、プリロードを使用します。
  • 今後のナビゲーション ルートやユーザー アクションに必要なリソースには、プリフェッチを使用します。

現在、すべてのブラウザがプリロードとプリフェッチの両方をサポートしているわけではありません。つまり、アプリケーションのすべてのユーザーがパフォーマンスの向上を実感できるわけではありません。

プリロードとプリフェッチがウェブページに与える影響について詳しくは、以下の記事をご覧ください。