POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

FeedlyRSSTwitterFacebook
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">が上に挿入されているため、優先度がLowimage-1.jpgファイルのほうが、優先度がHighstyle-1.cssファイルより先にダウンロードを開始しています。

preloadとfetchpriorityを組み合わせる

現時点でfetchPriorityに対応しているのはChromiumベースのブラウザだけですが、 fetchpriority属性を認識しない未対応ブラウザでも、機能はしないものの大きな支障は来しません。したがって、fetchPrioritypreloadディレクティブを組み合わせて使用することができます。

<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が悪化しています。

上のチャートは、優先度がLowHighのリソースについて、モバイル端末の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ディレクティブを使用しても画像のダウンロードは早くならない。

本稿に関するご意見やご感想をぜひお聞かせください。

おすすめリンク

謝辞

本稿の執筆にあたり、貴重なフィードバックとアドバイスをいただいたバリー・ポラード氏に心より感謝申し上げます。

注記

  • この機能は当初Priority Hintsと呼ばれていましたが、標準化後はFetch Priorityに名称が変更されました。
監修者
監修者_古川陽介
古川陽介
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
複合機メーカー、ゲーム会社を経て、2016年に株式会社リクルートテクノロジーズ(現リクルート)入社。 現在はAPソリューショングループのマネジャーとしてアプリ基盤の改善や運用、各種開発支援ツールの開発、またテックリードとしてエンジニアチームの支援や育成までを担う。 2019年より株式会社ニジボックスを兼務し、室長としてエンジニア育成基盤の設計、技術指南も遂行。 Node.js 日本ユーザーグループの代表を務め、Node学園祭などを主宰。