Didn't make the #ChromeDevSummit this year? Catch all the content (and more!) in the Chrome Dev Summit 2019 playlist on our Chrome Developers YouTube Channel.

リソースの優先度付け - ブラウザの有用性を高める

インターネットでブラウザに送信されるすべてのバイトが同じ重要性を持つわけではなく、ブラウザはそれを認識します。 ブラウザはヒューリスティックによって、最初に読み込むべき最も重要なリソース(たとえば、スクリプトや画像より先に CSS を読み込むなど)をできるだけ正確に推測します。

とはいえ、どんなヒューリスティックも常に正確とは限りません。通常、読み込みの時点で十分な情報がないために、ブラウザが誤った判断をする場合があります。 この記事では、後で必要になるコンテンツの優先度を最新のブラウザに認識させることにより、優先度を適切に調整する方法について説明します。

ブラウザにおけるデフォルトの優先度

前述のとおり、ブラウザは推測するリソースの重要度に応じて、さまざまなタイプのリソースに異なる相対的な優先度を割り当てます。 たとえば、ページの <head> にある <script> タグは優先度(最高である CSS に次ぐ優先度)で Chrome に読み込まれますが、async 属性が含まれている場合には、優先度がに変更されます(つまり、非同期で読み込まれ実行される可能性があります)。

サイトにおける読み込みパフォーマンスを調べる際、優先度が重要になります。 クリティカル レンダリング パスの測定分析といった通常の技法に加え、各リソースに対する Chrome の優先度を知ることは有用です。 優先度は、Chrome デベロッパー ツールの [Network] パネルで確認できます。 次のように表示されます。

Chrome デベロッパー ツールに表示される優先度の例
図 1: Chrome デベロッパー ツールに表示された優先度。 表示されない場合は、列見出しを右クリックして [Priority] 列を有効にする必要があります。

これらの優先度によって、ブラウザが各リソースに割り当てている相対的重要度がわかります。 わずかな違いがあるだけで、ブラウザが割り当てる優先度も異なります。たとえば、最初のレンダリングで処理される画像は、画面外で起動される画像よりも優先されます。 優先度の詳細については、Addy Osmani による記事をご覧ください。 Chrome における現在の優先度について詳しく紹介されています。

では、リソースに指定されている優先度が、自分の望む優先度と異なる場合は、どうすればよいでしょうか?

この記事では、3 つの異なる宣言型ソリューションについて詳しく説明します。どれも比較的新しい <link> タイプです。 ユーザー エクスペリエンスにとって非常に重要なリソースの読み込み優先度が低すぎる場合は、プリロードかプリコネクトのいずれかの方法で修正してみてください。 一方、他の処理がすべて終わったときにのみブラウザでリソースをフェッチさせるには、プリフェッチを試してください。

では、3 つの方法を詳しく見ていきましょう。

プリロード

<link rel="preload"> は、現在のナビゲーションの一部としてリソースが必要であり、できるだけ早くフェッチする必要があることをブラウザに通知します。 使い方は次のとおりです。

<link rel="preload" as="script" href="super-important.js">
<link rel="preload" as="style" href="critical.css">

as 属性以外の部分はおそらく予想どおりでしょう。 as 属性により、読み込むリソースのタイプをブラウザに知らせ、適切に処理されるようにすることができます。 正しいタイプが設定されていない場合、ブラウザはプリロードされたリソースを使用しません。 リソースはこの方法を使用しなかった場合と同じ優先度で読み込まれますが、ブラウザはリソースについて事前に認識しているので、ダウンロードを早く開始できます。

<link rel="preload"> はブラウザへの強制的な指示です。ここで取り上げる他のリソースヒントとは異なり、ブラウザが任意に選択できるヒントではなく、実行しなければならないものです。 そのため、これを使用することで誤って何かを 2 回フェッチしたり、不要なものをフェッチしたりしないように、慎重にテストすることが特に重要になります。

<link rel="preload"> を使用してフェッチされたリソースが 3 秒以内に現在のページで使用されない場合、Chrome デベロッパー ツールのコンソールに警告が表示されるので注意してください。

Chrome デベロッパー ツールのプリロード タイムアウト エラーの例

ユースケース:フォント

フェッチする必要があるのに、発見されるのが遅いリソースの一例が、フォントです。フォントは多くの場合、ページによって読み込まれた複数の CSS ファイルのうちの 1 つの末尾に指定されています。

サイトのテキスト コンテンツをユーザーが待機する時間を短縮し、システム フォントと指定フォントが切り替わる目障りなちらつきを防止するために、HTML で <link rel="preload"> を使用してブラウザに必要なフォントの存在を即座に知らせることができます。

<link rel="preload" as="font" crossorigin="crossorigin" type="font/woff2" href="myfont.woff2">

ここで重要なのは crossorigin を使用していることです。この属性がないと、プリロードされたフォントはブラウザで無視され、新たなフェッチが行われます。 これは、フォントはブラウザにより匿名でフェッチされることが予期されているためです。プリロード リクエストは、crossorigin 属性を指定した場合にのみ匿名になります。

ユースケース:クリティカル パス CSS と JavaScript

ページのパフォーマンスを考慮するうえで、「クリティカル パス」の概念は役立ちます。 クリティカル パスとは、最初のレンダリングの前に読み込むべきリソースを指します。 このようなリソース(CSS など)は、ユーザーの画面に表示される最初のピクセルを取得するために必要不可欠です。

以前は、この種のコンテンツを HTML 内でインライン化することが推奨されていました。 しかし、複数ページをサーバー側でレンダリングするシナリオでは、すぐに大量のバイトが無駄になってしまいます。 クリティカルなコードに変更を加えると、それをインライン化したページがすべて無効になるので、バージョニングも困難になります。

<link rel="preload"> を使用すれば、個々のファイルでバージョニングとキャッシュ処理を行う利点を活かしたまま、できるだけ早くリソースを要求するメカニズムを実装できます。

<link rel="preload" as="script" href="super-important.js">
<link rel="preload" as="style" href="critical.css">

プリロードには、短所が 1 つあります。それは、依然として余分なラウンドトリップが発生することです。 この余分なラウンドトリップは、ブラウザが最初に HTML をフェッチしなければならず、その後に次のリソースを見つけるために生じます。

これを回避する 1 つの方法は、代わりに HTTP/2 プッシュを使用することです。これは、HTML を送信するのと同じ接続に対して、クリティカルなアセットをあらかじめ添付する方法です。 これにより、ユーザーのブラウザが HTML を取得してからクリティカルなアセットのダウンロードを開始するまでの間にダウンタイムが発生しないことが保証されます。 ただし、HTTP/2 プッシュを使用する際は注意が必要です。これは、ユーザーの帯域幅使用をかなり強引に制御する(「サーバーの判断を最優先」とする)方法であり、ブラウザが独自の判断(既にキャッシュにあるファイルを取得しないなど)を下す余地がほとんどないためです。

プリコネクト

<link rel="preconnect"> は、ページが他のオリジンへの接続を確立しようとしていることと、プロセスをできるだけ早く開始する必要があることをブラウザに通知します。

大抵、低速ネットワークでは接続の確立(特に、安全な接続を行う場合)に時間がかかります。これは、DNS ルックアップやリダイレクトのほか、ユーザーのリクエストを処理する最終サーバーへの数回のラウンドトリップが発生する可能性があるためです。 これらすべてを事前に処理することで、帯域幅の使用に悪影響を与えることなく、アプリケーションの速度を上げることができます。 接続の確立に要する時間の大部分は、データのやりとりではなく待機に費やされます。

以下のようにページにリンクタグを追加するだけで、簡単にブラウザへの通知を行うことができます。

<link rel="preconnect" href="https://example.com">

この例では、example.com に接続してそこからコンテンツを取得したいという意図をブラウザに伝えています。

<link rel="preconnect"> はかなり低コストであるものの、安全な接続では特に、貴重な CPU 時間を消費する可能性があることに注意してください。 この点は、10 秒以内に接続が使用されない場合は特に重要です。ブラウザが接続をクローズし、初期段階の接続作業がすべて無駄になってしまうからです。

一般的には、可能であれば、より幅広く使用できるパフォーマンス調整方法として <link rel="preload"> を使ってください。ただし、特殊なケースでは <link rel="preconnect"> も選択肢として検討することができます。 これら 2 つの方法を見てみましょう。

注: 実際には、接続に関連する <link> タイプはもう 1 つあります。 それは <link rel="dns-prefetch"> です。 これは DNS ルックアップのみを処理するものなので、<link rel="preconnect"> の小さなサブセットといえます。ただし、ブラウザからより広範なサポートが得られるため、便利な代替策として使用できる場合があります。

次に示すように、使い方は同じです。 <link rel="dns-prefetch" href="https://example.com">

ユースケース:どこから取得するかはわかっているが何をフェッチするかがわからない場合

依存関係がバージョニングされているために、特定の CDN からリソースを取得することはわかっていても、リソースへの正確なパスがわからない場合があります。 また、ユーザーのブラウザにおけるメディアクエリやランタイム機能チェックに応じて、複数のリソースのいずれかが取得される場合もあります。

このような状況では、フェッチするリソースが重要であれば、サーバーに事前接続することで、できるだけ時間を節約することができます。 ブラウザは、ファイルが必要になる(つまり、何らかの方法でページから要求される)前にファイルのフェッチを開始することはありませんが、少なくとも事前に接続作業を処理できるので、ユーザーは数回のラウンドトリップを待機せずに済みます。

ユースケース:メディアのストリーミング

接続フェーズの時間を短縮したいが、必ずしもコンテンツの取得をすぐには開始しない別の例として、別のオリジンからメディアをストリーミングする場合があります。

ストリーミングされたコンテンツをページが処理する方法によっては、スクリプトが読み込まれてストリームを処理する準備が整うまで待つほうがよいこともあります。 プリコネクトを使用することにより、フェッチを開始する準備が整ったら、待機時間を 1 回のラウンドトリップのみに短縮することができます。

プリフェッチ

<link rel="prefetch"><link rel="preload"><link rel="preconnect"> とは少し異なり、クリティカルなリソースを早めに取得しようとするものではありません。代わりに、機会があればクリティカルでないリソースを早めに取得しようとします。

この処理は、今後のナビゲーションやユーザー操作で必要になると予想されるリソース(たとえば、予想されるアクションをユーザーが後で実行した場合に必要になる可能性があるもの)をブラウザに通知することによって行われます。 それらのリソースは、現在のページの読み込みが完了し、利用可能な帯域幅がある場合に、Chrome の最低優先度でフェッチされます。

つまり、prefetch は、ユーザーが次に実行するであろう処理を先取りしてその処理に備える場合に最適です。たとえば、結果リストの最初の商品詳細ページを取得したり、ページ区切り付きコンテンツの次のページを取得したりする場合などです。

<link rel="prefetch" href="page-2.html">

プリフェッチは再帰的に動作しないことに注意してください。 上記の例では、HTML が取得されるだけです。page-2.html が必要とするリソースは、明示的にプリフェッチしない限り、事前にダウンロードされることはありません。

プリフェッチはオーバーライドとして動作しない

重要な点として、<link rel="prefetch"> は既存のリソースの優先度を下げる方法には使用できないということに注意してください。 次の HTML では、プリフェッチで optional.css を宣言すると、続く <link rel="stylesheet"> の優先度が下がるように思えます。

<html>
  <head>
    <link rel="prefetch" href="optional.css">
    <link rel="stylesheet" href="optional.css">
  </head>
  <body>
    Hello!
  </body>
</html>

しかし実際には、プリフェッチにより別個にフェッチが起動されるため、スタイルシートは 2 回(デフォルトの最高優先度で 1 回、最低優先度で 1 回)フェッチされます(ただし、2 回目でキャッシュがヒットする可能性があります)。

optional.css
が 2 回フェッチされたことを示す Chrome デベロッパー ツールのスクリーンショット

二重フェッチはユーザーにとって不都合な場合があります。 レンダリング ブロック CSS を待機しなければならないだけでなく、同じファイルを 2 回ダウンロードすることで帯域幅が浪費される可能性もあるからです。 ユーザーの帯域幅は従量制課金のために計測されている可能性があることを忘れてはなりません。 ネットワーク リクエストを十分に分析し、二重フェッチが起こらないように注意しましょう。

その他の技法とツール

<link rel="preload"><link rel="preconnect"><link rel="prefetch">(さらに補助手段としての <link rel="dns-prefetch">)は、リソースと接続に関する情報をブラウザに前もって宣言し、それらがいつ必要になるかに応じて処理のタイミングを調整するのに大変役立ちます。

リソースを読み込む優先度とタイミングを調整するためのツールと技法は、他にも多くあります。 HTTP/2 サーバー プッシュIntersectionObserver を使用して画像などのメディアを遅延読み込みする方法、メディアクエリを使用して レンダリング ブロック CSS を回避する方法loadCSSなどのライブラリを参照してください。 また、JavaScript のフェッチ、コンパイル、実行を遅延させるために asyncdefer を使用する方法も参照してください。

フィードバック

Was this page helpful?
Yes
What was the best thing about this page?
It helped me complete my goal(s)
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had the information I needed
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had accurate information
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was easy to read
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
Something else
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
No
What was the worst thing about this page?
It didn't help me complete my goal(s)
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was missing information I needed
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had inaccurate information
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was hard to read
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
Something else
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.