2023年10月30日
fetchPriorityと、LCPの最適化
(2023-01-02)by Kevin Farrugia
本記事は、原著者の許諾のもとに翻訳・掲載しております。
fetchPriority APIは、リソースの相対的な優先度をブラウザに示すために使用します。fetchpriority属性を<img>
、<link>
、<script>
、<iframe>
の各要素に追加するか、Fetch API上でpriority
属性を使用することで優先度を設定できます。
ブラウザのロードプロセスは複雑です。ブラウザは、主にリクエストの種類や、ドキュメントのマークアップ内におけるリクエストの位置によってその優先度を判断します。例えば、ドキュメントの<head>
内で要求されたCSSファイルの優先度はHighest
となり、defer
属性が設定された<script>
要素の優先度はLow
となります。ブラウザは、同じ優先度が割り当てられたリソースを、検出した順にダウンロードします。
fetchpriority
fetchpriority属性を使用することで、要求されたリソースの優先度を高くしたり低くしたりするようブラウザに指示できます。これは列挙型の属性であり、以下の3つの内いずれかの値を指定できます。
- high – リソースの優先度がデフォルトの優先度よりも高い
- low – リソースの優先度がデフォルトの優先度よりも低い
- auto – デフォルト値
<img src="/lcp.jpg" alt="A dog" fetchpriority="high" />
上の例では、<img>
の優先度がデフォルトの優先度よりも高いことをブラウザに示しています。
fetch
メソッドのpriority属性でも同じ値を指定できます。
fetch("/api/data.json", { priority: 'high' })
上の[fetch]リクエストでは、リクエストの優先度がデフォルトの優先度よりも高いことをブラウザに示しています。
デフォルトの優先度
fetchPriority APIを使用することで、デフォルトの優先度に対してリソースの優先度を高くしたり低くしたりすることができます。例えば、画像のデフォルトの優先度はLow
ですが、fetchpriority="high"
と指定することで優先度をHigh
に変更できます。一方、レンダーブロッキングとなるスタイルシートのデフォルトの優先度はHighest
です。fetchpriority="low"
と指定すると、優先度はLow
ではなくHigh
に変更されます。fetchpriority
は、値を明示的に設定するのではなく、デフォルトを基準としてリソースの優先度を調整するために使用します。
influence of Fetch Priority on resource prioritization in Chromiumという文書には、各種リソースタイプ、デフォルトの優先度(◉)、fetchpriority="high"
(⬆)およびfetchpriority="low"
(⬇)を使用した場合の優先度が記されています。
ビューポート内にあることが判明した画像の優先度はHighに引き上げられます。しかし、その時点ではロードプロセスがかなり進んでいる可能性もあり、すでにリクエストを送信済みの場合はほとんど影響がないか、まったく影響がない可能性もあります。fetchpriority="high"を使用することで、画像がビューポート内にあるかどうかをブラウザが判断するまで待つのではなく、最初から優先度をHighに指定することができます。
「タイトモード」
ほとんどのブラウザは、2つのフェーズに分けてリソースをダウンロードします。初期フェーズ(Chromiumでは「タイトモード」とも呼ばれます)では、実行中のリクエストの数が2つ未満の場合を除き、優先度がLow
のリソースはダウンロードされません。
上のウォーターフォールチャートを見ると、リソースimage-1.jpg
のダウンロードは、直ちに検出されたとしてもstyle-2.css
のダウンロードが完了するまで開始されないことが分かります。この時点でダウンロード中のリソースはscript.js
のみのため、優先度がLow
の画像のダウンロードが開始されます。
初期フェーズは、<head>
内のブロッキングスクリプトが全てダウンロードされ、実行された時点で完了します(async
またはdefer
が設定されたスクリプトはレンダーブロッキングではありません)。それ以降は、実行中のリクエストの数が2つ以上ある場合でも、残りのリソースの優先度とマークアップ内における順序に基づいてダウンロードが行われます。
上のチャートでは、レンダーブロッキングとなるJavaScriptがダウンロードされ、実行(ピンク色のバー)された時点で、2つのCSSファイルのダウンロードがまだ進行中であるものの、画像のダウンロードが開始します。黄色の縦線は、readystatechange
イベントが発生したタイミング、すなわちDOMインタラクティブを示しています。
preconnect
画像が別のドメイン上に存在する場合、ブラウザはファイルをダウンロードする前にドメインへの接続を開く必要があります。
これは、WebPageTestチャート上でダウンロードの前にある緑色、オレンジ色、赤紫色のバーによって示されています。preconnectリソースヒントを使用することで、画像のダウンロード開始を早めることができます。
上のチャートでは、ブラウザがファイルのダウンロードを開始できる前に、初期フェーズでcdn.glitch.global
ドメインへの接続が開かれています。初期フェーズが終了した時点(黄色の縦線)で、直ちに画像のダウンロードが開始されます。これにより、約350ミリ秒の節約になります。
preload
preconnect
リソースヒントを使用してダウンロード時間を短縮できるのであれば、preloadディレクティブを使用することでさらに短縮できるのでしょうか。結論から言うとできません。preloadディレクティブは、「遅れて検出」される重要なリソースについて、ブラウザに情報を伝えるために使用できます。これは、スタイルシートやスクリプトに組み込まれた背景画像やフォントなどのリソースを読み込む際に特に役立ちます。ここで紹介する例では、画像はマークアップ内で宣言され、早い段階で検出されるため、プリロードを行ってもほとんど影響がありません。
上のチャートでは、preconnect
ヒントを以下に置き換えました。
<link
rel="preload"
as="image"
href="https://cdn.glitch.global/.../image-1.jpg"
/>
プリロードを行ったにもかかわらず、処理中のリクエストの数が2つ未満になるまで画像のダウンロードは開始されません。
fetchpriority
以下のように、fetchPriorityを使用することで、image-1.jpg
がデフォルトの優先度よりも重要であることをブラウザに示すことができます。
<img
src="https://cdn.glitch.global/.../image-1.jpg"
fetchpriority="high"
alt=""
/>
これにより、最初から画像の優先度をLow
からHigh
に高め、初期フェーズで画像を読み込ませることができます。
上のウォーターフォールチャートでは、image-1.jpg
が他の重要なリソースと並行して初期フェーズで読み込まれています。これにより、ここまでで最も大きな改善が得られます。
Firefox
Firefoxも、同様の方法を用いて初期フェーズで読み込むリソースを判断しています。しかし、Chromiumベースのブラウザとは異なり、<head>
内のJavaScriptが全てダウンロードされ、実行されるまで、優先度がLow
のリソースのダウンロードは開始されません。これは、優先度がHigh
のリクエストが1つしか進行していない場合でも同じです。
上はFirefox Webデベロッパーツールのスクリーンショットです。これによると、画像リソース(5行目〜8行目)はスクリプト(2行目)のダウンロードと実行が完了し、ページがinteractive
(青い縦線)になってから取得されることが分かります。
Chromeは<head>
内で宣言されたJavaScriptがダウンロードされ、実行されるまで待ちますが、Firefoxは、<head>
内で宣言されたものでなくても、画像要素より前に宣言された全てのレンダーブロッキングとなるJavaScriptがダウンロードされ、実行されるまで待ちます。
Firefoxはまだfetchpriorityに対応していませんが、preload
ディレクティブを使用することでimage-1.jpg
の優先度を高めることができます。
上のスクリーンショットでは、他のリソースと並行してimage-1.jpg
ファイルを取得しています。これは、Google Chromeでfetchpriority="high"
を追加した際に見た挙動と似ています。
Safari
iOSやmacOS上で動作するSafariにも初期フェーズはありますが、ChromeやFirefoxとは挙動が異なります。
優先度がLow
のリソースは、処理中のリクエストの数が2つ未満になると取得を開始します。readystatechange
イベントに依存せず、レンダーブロッキングとなるJavaScriptがないページでも、処理中のリクエストが1つになるまで待ちます。
上はSafariのWebインスペクタのスクリーンショットです。ここでは、style-1.css
のダウンロードが完了し、処理中のリクエストの数が2つ未満になるまで画像のダウンロードは開始していません。
Safariでは、初期フェーズは同じドメイン上にあるリソースにしか適用されません。優先度がLow
のリソースが別のドメインから読み込まれる場合、検出された時点で取得されます。
上のスクリーンショットでは、優先度がHigh
のリソースのダウンロードが終わるまで待つことなく、crossorigin属性の画像が直ちに取得されています。
preload
ディレクティブはリソースの優先度に影響しませんが、検出された時点で処理中のリクエストの数が2つ未満なので、優先度がHigh
のリクエストの前に<link rel="preload">
ディレクティブを入れることでダウンロードを早めることができます。この挙動は他のブラウザと同じですが、レンダーブロッキングとなるCSSが優先されるべきなので、ほとんどの場合、優先度がHigh
のリソースの上にpreload
ディレクティブを入れることは推奨しません。
このスクリーンショットでは、ドキュメントのマークアップ内で<link rel="preload">
が上に挿入されているため、優先度がLow
のimage-1.jpg
ファイルのほうが、優先度がHigh
のstyle-1.css
ファイルより先にダウンロードを開始しています。
preloadとfetchpriorityを組み合わせる
現時点でfetchPriorityに対応しているのはChromiumベースのブラウザだけですが、
fetchpriority属性を認識しない未対応ブラウザでも、機能はしないものの大きな支障は来しません。したがって、fetchPriority
とpreload
ディレクティブを組み合わせて使用することができます。
<link
rel="preload"
as="image"
fetchpriority="high"
href="https://cdn.glitch.global/.../image-1.jpg"
/>
FetchPriorityに対応したブラウザは、割り当てられたfetchpriority
属性を使用してリソースをプリロードし、未対応のブラウザはpreload
ディレクティブを使用します。
上のチャートは、<img>
要素にfetchpriority属性を含んだ前述の例と似た結果を示しています。この方法の利点は、fetchPriority
に対応したブラウザと未対応のブラウザ上のリソースを優先順位付けするアプローチを統一できることにあります。
fetchpriorityに関するさまざまな考察
このセクションでは、fetchpriority
を使用する潜在的な利点について見ていきます。データは全てHTTP Archiveから取得したものであり、HTTP/2またはHTTP/3を使用し、LCP(Largest Contentful Paint)要素が画像のページについてのみ検討します。クエリと結果は全て公開されています。
注記:HTTP Archiveのデータは、Chrome上でWebPageTestのプライベートインスタンスを使用して収集したものです。データの収集方法に関する詳細はこちらをご覧ください。
fetchpriority
の利点は、リソースが検出されてからダウンロードが開始されるまでの時間差にあると考えられます。筆者はこれをopportunityと呼んでいます。したがって、早い段階でリソースが検出されたものの、ダウンロードが始まるのが遅い場合、その分opportunityは大きくなります。
注記:画像が異なるドメインからダウンロードされる場合、接続を開くための時間もopportunityの中に含めています。
上のチャートでは、LCPに対するopportunity(ミリ秒単位)を図示しています。opportunityは100ミリ秒単位のバケットに分け、1,000ミリ秒超のopportunityは1つのバケットにまとめています。チャートはopportunityとLCPの間に強い相関関係を示しており、opportunityが大きいほどLCPが悪化しています。
上のチャートは、優先度がLow
とHigh
のリソースについて、モバイル端末のopportunityの分布を示しています。中央値では、優先度がHigh
のLCP画像は検出された21ミリ秒後にダウンロードが始まっていますが、優先度がLow
のLCP画像は102ミリ秒後にダウンロードされています。75パーセンタイルや90パーセンタイルになるとその差はさらに開きます。
fetchpriority="High"の他にも、画像の検出が遅い場合(CSS background-imageを使用する場合やJavaScriptを使用して画像を追加する場合)には優先度の初期設定がHighになっている可能性があります。その場合は、リクエストの優先度がすでにHighに設定されているため、fetchpriorityは役に立ちません。
結論としては、LCP画像の優先順位付けを行うことには明確な利点があります。opportunityはページの構成によって異なります。優先度がLowのリソースは、レンダーブロッキングとなるスクリプトが1つ以上、処理中のリクエストが2つ以上ある場合、すぐに取得されないことは前述の通りです。
上のチャートは、opportunity(ミリ秒単位)に対するレンダーブロッキングとなるリソースの数を図示しています。ページ上にあるレンダーブロッキングとなるリソースの数が多いほど、LCP画像のダウンロードが遅れることが直感的に分かります。
まとめ
リソースヒントやfetchPriorityを使用することで、LCP画像の優先順位付けを行う大きなチャンスがあります。メインドキュメント上ですぐに検出できる場合でも、LCP要素がキューで待機中のページは少なくありません。
上のチャートを見ると、中央値のモバイルサイトではLCP画像のダウンロードが開始されるまで98ミリ秒の待機時間があります。90パーセンタイルでは810ミリ秒です。fetchPriorityを使用すれば、LCP画像の優先度を高め、待機時間を減らすことができます。
また、LCP画像にfetchpriority="high"
を追加するとLCP(Largest Contentful Paint)が改善することを示す事例もあります。Etsyは4%の改善を報告しており、20〜30%の改善を報告している企業もあります。
あるリソースの優先度を高めると、大抵は別のリソースにしわ寄せが来るため、fetchPriorityは控えめに使用するべきでしょう。しかし、ブラウザがLCP画像をキューに入れているようであれば、fetchPriorityを使用することで待機時間を減らし、LCPを改善できるか試してみることをお勧めします。
以下にポイントをまとめます。
- LCP画像はHTMLドキュメントと同じドメイン上にホスティングする。それが難しい場合、
preconnect
を使用して早い段階で接続を開く。 - LCP画像はドキュメントのマークアップの一部を構成することが望ましい。それが難しい場合、
preload
を使用し、リクエストされる前に画像をダウンロードするようブラウザに指示する。 - リソースは極力ブロックしないこと。優先度が
Low
のLCP画像をダウンロードする場合、fetchpriority
を使用してダウンロードを早めるようブラウザに指示する。 - Firefoxでは、
fetchpriority
に対応するまではpreload
を使用してLCP画像の優先度を高めることができる。Safariでは、preload
ディレクティブを使用しても画像のダウンロードは早くならない。
本稿に関するご意見やご感想をぜひお聞かせください。
おすすめリンク
- Demos
- Queries & Results
- Optimizing resource loading with Priority Hints (web.dev)
- Prioritizing Important Page Resources With Priority Hints (debugbear.com)
謝辞
本稿の執筆にあたり、貴重なフィードバックとアドバイスをいただいたバリー・ポラード氏に心より感謝申し上げます。
注記
- この機能は当初Priority Hintsと呼ばれていましたが、標準化後はFetch Priorityに名称が変更されました。
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa