どこでも高速なサイトを開発するのは難しいことです。デバイスの機能の多さと、接続先のネットワークの品質により、このタスクは困難に思えるかもしれません。ブラウザの機能を利用して読み込みパフォーマンスを向上させることはできますが、ユーザーのデバイスの性能やネットワーク接続の品質をどのように把握すればよいのでしょうか?解決策はクライアント ヒントです。
クライアント ヒントは、ユーザーのデバイスと接続先のネットワークのこれらの側面に関する分析情報を提供する、オプトインの HTTP リクエスト ヘッダーのセットです。この情報をサーバーサイドで利用することで、デバイスやネットワークの状況に応じてコンテンツの配信方法を変更できます。これにより、より包括的なユーザー エクスペリエンスを実現できます。
コンテンツ ネゴシエーションについて
クライアント ヒントは、コンテンツ ネゴシエーションの別の方法です。つまり、ブラウザのリクエスト ヘッダーに基づいてコンテンツ レスポンスを変更します。
コンテンツ ネゴシエーションの例として、Accept リクエスト ヘッダーがあります。ブラウザが理解できるコンテンツのタイプを記述します。サーバーはこれを使用してレスポンスをネゴシエートできます。画像リクエストの場合、Chrome の Accept ヘッダーの内容は次のようになります。
Accept: image/webp,image/apng,image/*,*/*;q=0.8
すべてのブラウザが JPEG、PNG、GIF などの画像形式をサポートしていますが、この場合、Accept はブラウザが WebP と APNG もサポートしていることを示しています。この情報を使用して、各ブラウザに最適な画像タイプをネゴシエートできます。
<?php
// Check Accept for an "image/webp" substring.
$webp = stristr($_SERVER["HTTP_ACCEPT"], "image/webp") !== false ? true : false;
// Set the image URL based on the browser's WebP support status.
$imageFile = $webp ? "whats-up.webp" : "whats-up.jpg";
?>
<img src="<?php echo($imageFile); ?>" alt="I'm an image!">
Accept と同様に、クライアント ヒントはコンテンツをネゴシエートする別の方法ですが、デバイスの機能とネットワークの状況というコンテキストでネゴシエートします。クライアント ヒントを使用すると、ネットワーク環境が悪いユーザーに重要でないリソースを配信するかどうかを決定するなど、ユーザーの個々のエクスペリエンスに基づいてサーバーサイドのパフォーマンスを決定できます。このガイドでは、利用可能なすべてのヒントと、それらを使用してコンテンツ配信をユーザーにとってより使いやすくする方法について説明します。
オプトイン
Accept ヘッダーとは異なり、クライアント ヒントは自動的に表示されるわけではありません(Save-Data を除く。これについては後で説明します)。リクエスト ヘッダーを最小限に抑えるため、ユーザーがリソースをリクエストする際に Accept-CH ヘッダーを送信して、受け取るクライアント ヒントを選択する必要があります。
Accept-CH: Viewport-Width, Downlink
Accept-CH の値は、サイトが後続のリソース リクエストの結果を決定する際に使用するリクエストされたヒントのカンマ区切りリストです。クライアントがこのヘッダーを読み取ると、「このサイトは Viewport-Width と Downlink のクライアント ヒントを必要としている」と伝えられます。具体的なヒント自体については心配しないでください。これについては後ほど説明します。
これらのオプトイン ヘッダーは、任意のバックエンド言語で設定できます。たとえば、PHP の header 関数を使用できます。<meta> タグの http-equiv 属性を使用して、これらのオプトイン ヘッダーを設定することもできます。
<meta http-equiv="Accept-CH" content="Viewport-Width, Downlink" />
すべてのクライアント ヒント!
クライアント ヒントは、ユーザーが使用するデバイスと、ユーザーがサイトにアクセスするために使用するネットワークのいずれかを表します。利用可能なすべてのヒントについて簡単に説明します。
デバイスに関するヒント
一部のクライアント ヒントは、ユーザーのデバイスの特性(通常は画面の特性)を表します。一部のものは、特定のユーザーの画面に最適なメディア リソースを選択するのに役立ちますが、必ずしもメディア中心ではありません。
このリストに入る前に、画面とメディアの解像度を説明するために使用されるいくつかの重要な用語を理解しておくとよいでしょう。
固有サイズ: メディア リソースの実際の寸法。たとえば、Photoshop で画像を開くと、画像サイズ ダイアログに表示される寸法は、その固有のサイズを表します。
密度補正後の固有サイズ: ピクセル密度を補正した後のメディア リソースの寸法。画像の固有サイズをデバイスのピクセル比率で割った値です。たとえば、次のマークアップがあるとします。
<img
src="whats-up-1x.png"
srcset="whats-up-2x.png 2x, whats-up-1x.png 1x"
alt="I'm that image you wanted."
/>
この場合、1x 画像の固有サイズは 320x240、2x 画像の固有サイズは 640x480 とします。このマークアップが、画面デバイスのピクセル比率が 2 のデバイス(Retina ディスプレイなど)にインストールされたクライアントによって解析されると、2x 画像がリクエストされます。2x 画像の密度補正後の固有サイズは 320x240 です。640x480 を 2 で割ると 320x240 になるためです。
外部サイズ: CSS やその他のレイアウト要素(width 属性や height 属性など)が適用された後のメディア リソースのサイズ。たとえば、密度補正された固有サイズが 320×240 の画像を読み込む <img> 要素があり、CSS の width プロパティと height プロパティにそれぞれ 256px と 192px の値が適用されているとします。この例では、その <img> 要素の外部サイズは 256x192 になります。
width: 256px; と height: 192px; の CSS ルールを適用すると、320x240 の固有サイズの画像が 256x192 の外部サイズの画像に変換されます。用語をいくつか理解したところで、利用可能なデバイス固有のクライアント ヒントのリストを見ていきましょう。
Viewport-Width
Viewport-Width は、ユーザーのビューポートの幅(CSS ピクセル単位)です。
Viewport-Width: 320
このヒントは、他の画面固有のヒントと組み合わせて使用できます。これにより、特定の画面サイズに最適な画像処理(切り抜きなど)を適用したり(アート ディレクション)、現在の画面幅に不要なリソースを省略したりできます。
DPR
DPR(デバイス ピクセル比)は、ユーザーの画面の物理ピクセルと CSS ピクセルの比率をレポートします。
DPR: 2
このヒントは、画面のピクセル密度に対応する画像ソースを選択する場合に便利です(srcset 属性の x ディスクリプタと同様)。
幅
Width ヒントは、sizes 属性を使用して <img> タグまたは <source> タグによって起動された画像リソースのリクエストに表示されます。sizes は、リソースの外部サイズをブラウザに伝えます。Width は、その外部サイズを使用して、現在のレイアウトに最適な内部サイズの画像をリクエストします。
たとえば、ユーザーが 320 CSS ピクセル幅の画面で DPR が 2 のページをリクエストしたとします。デバイスは、sizes 属性値が 85vw(つまり、<img>すべての画面サイズでビューポート幅の 85%)。Width ヒントが有効になっている場合、クライアントは <img> の src のリクエストとともに、この Width ヒントをサーバーに送信します。
Width: 544
この場合、クライアントは、リクエストされた画像の最適な固有幅がビューポート幅(272 ピクセル)の 85% に画面の DPR(2)を掛けた 544 ピクセルであることをサーバーにヒントとして伝えています。
このヒントは、画面の密度補正後の幅を考慮するだけでなく、この重要な情報をレイアウト内の画像の外部サイズと調整するため、特に強力です。これにより、サーバーは画面とレイアウトの両方に最適な画像レスポンスをネゴシエートできます。
Content-DPR
画面にデバイスのピクセル比があることはすでにご存じでしょうが、リソースにも独自のピクセル比があります。最も単純なリソース選択のユースケースでは、デバイスとリソースのピクセル比率が同じになることがあります。ただし、DPR ヘッダーと Width ヘッダーの両方が使用されている場合、リソースの外部サイズによって、2 つのヘッダーの値が異なるシナリオが発生する可能性があります。ここで Content-DPR ヒントが役立ちます。
他のクライアント ヒントとは異なり、Content-DPR はサーバーで使用されるリクエスト ヘッダーではなく、DPR ヒントと Width ヒントを使用してリソースを選択するたびにサーバーが送信しなければならないレスポンス ヘッダーです。Content-DPR の値は、この方程式の結果である必要があります。
Content-DPR = [選択した画像リソースのサイズ] / ([Width] / [DPR])
Content-DPR リクエスト ヘッダーが送信されると、ブラウザは画面のデバイス ピクセル比とレイアウトに合わせて指定された画像をスケーリングする方法を認識します。これがないと、画像が正しくスケーリングされない可能性があります。
Device-Memory
技術的には Device Memory API の一部である Device-Memory は、現在のデバイスが搭載している おおよそのメモリ量を GiB 単位で明らかにします。
Device-Memory: 2
このヒントのユースケースとしては、メモリが限られたデバイスのブラウザに送信される JavaScript の量を減らすことが考えられます。JavaScript は、ブラウザが通常読み込むコンテンツ タイプの中で最もリソースを消費するからです。また、デコードに必要なメモリが少ない低 DPR の画像を送信することもできます。
ネットワーク ヒント
ネットワーク情報 API は、ユーザーのネットワーク接続のパフォーマンスを記述する別のカテゴリのクライアント ヒントを提供します。これらは最も有用なヒントのセットだと思います。これにより、低速接続のクライアントにリソースを配信する方法を変更して、ユーザーに合わせてエクスペリエンスを調整できます。
RTT
RTT ヒントは、アプリケーション レイヤでのおおよそのラウンドトリップ時間(ミリ秒単位)を提供します。RTT ヒントは、トランスポート レイヤの RTT とは異なり、サーバーの処理時間を含みます。
RTT: 125
このヒントは、読み込みパフォーマンスにおけるレイテンシの役割を考慮すると有用です。RTT ヒントを使用すると、ネットワークの応答性に基づいて判断を下すことができます。これにより、エクスペリエンス全体の配信を高速化できます(一部のリクエストを省略するなど)。
ダウンリンク
読み込みパフォーマンスではレイテンシが重要ですが、帯域幅も影響します。Downlink ヒントは、メガビット/秒(Mbps)で表され、ユーザーの接続の下り速度のおおよその値を示します。
Downlink: 2.5
RTT と組み合わせて使用すると、Downlink はネットワーク接続の品質に基づいてユーザーにコンテンツを配信する方法を変更する際に役立ちます。
エクアドル時間(ECT)
ECT ヒントは、効果的な接続タイプを表します。値は、接続タイプの列挙リストのいずれかです。各接続タイプは、RTT 値と Downlink 値の両方の指定範囲内の接続を表します。
このヘッダーは、実際の接続タイプを説明するものではありません。たとえば、ゲートウェイが携帯電話基地局か Wi-Fi アクセス ポイントかをレポートしません。現在の接続のレイテンシと帯域幅を分析し、どのネットワーク プロファイルに最も類似しているかを判断します。たとえば、Wi-Fi 経由で低速のネットワークに接続すると、ECT に 2g という値が入力されることがあります。これは、有効な接続に最も近い近似値です。
ECT: 2g
ECT の有効な値は、4g、3g、2g、slow-2g です。このヒントは、接続品質を評価する際の出発点として使用でき、その後 RTT ヒントと Downlink ヒントを使用して絞り込むことができます。
Save-Data
Save-Data は、ネットワークの状況を説明するヒントというよりは、ページで送信するデータ量を減らすべきであるというユーザー設定です。
Save-Data をネットワーク ヒントとして分類することをおすすめします。これは、Save-Data で行うことの多くが他のネットワーク ヒントと似ているためです。レイテンシが高く帯域幅が狭い環境では、ユーザーがこの機能を有効にする可能性が高くなります。このヒントは、存在する場合は常に次のようになります。
Save-Data: on
Google では、Save-Data でできることについて説明してきました。パフォーマンスに大きな影響を与える可能性があります。ユーザーが文字どおり「送るものを減らしてほしい」と求めているシグナルです。そのシグナルをリッスンして対応すれば、ユーザーは感謝してくれるでしょう。
まとめ
クライアント ヒントをどのように使用するかは、ユーザー次第です。多くの情報が提供されるため、多くの選択肢があります。アイデアを出すために、中西部の田舎にある架空の木材会社 Sconnie Timber でクライアント ヒントがどのように役立つかを見てみましょう。遠隔地ではよくあることですが、ネットワーク接続が不安定になることがあります。クライアント ヒントのようなテクノロジーがユーザーにとって真に役立つのは、このような場合です。
レスポンシブ画像
最もシンプルなレスポンシブ画像のユースケース以外は、複雑になる可能性があります。異なる画面サイズや異なる形式に対応するため、同じ画像の複数のトリートメントとバリエーションがある場合はどうすればよいですか?このマークアップは、非常に複雑なものに非常に迅速になります。間違えやすく、重要なコンセプト(sizes など)を忘れやすい、または誤解しやすい。
<picture> と srcset は間違いなく優れたツールですが、複雑なユースケースでは開発とメンテナンスに時間がかかることがあります。マークアップの生成を自動化することはできますが、<picture> と srcset が提供する機能は複雑であるため、自動化を行うには、それらが提供する柔軟性を維持する方法で行う必要があります。
クライアント ヒントを使用すると、この処理を簡素化できます。クライアント ヒントを使用した画像レスポンスのネゴシエーションは、次のようになります。
- ワークフローに該当する場合は、まず
Viewport-Widthヒントをオンにして、画像処理(アート ディレクションされた画像など)を選択します。 WidthヒントとDPRヒントを確認し、画像のレイアウト サイズと画面密度に合ったソースを選択して、画像の解像度を選択します(srcsetでのxとwの記述子の動作と同様です)。- ブラウザがサポートする最適なファイル形式を選択します(ほとんどのブラウザでは
Acceptがこの処理をサポートしています)。
架空の木材会社のお客様の件では、クライアント ヒントを使用する単純なレスポンシブ画像選択ルーチンを PHP で開発しました。つまり、このマークアップをすべてのユーザーに送信する代わりに、
<picture>
<source
srcset="
company-photo-256w.webp 256w,
company-photo-512w.webp 512w,
company-photo-768w.webp 768w,
company-photo-1024w.webp 1024w,
company-photo-1280w.webp 1280w
"
type="image/webp"
/>
<img
srcset="
company-photo-256w.jpg 256w,
company-photo-512w.jpg 512w,
company-photo-768w.jpg 768w,
company-photo-1024w.jpg 1024w,
company-photo-1280w.jpg 1280w
"
src="company-photo-256w.jpg"
sizes="(min-width: 560px) 251px, 88.43vw"
alt="The Sconnie Timber Staff!"
/>
</picture>
個々のブラウザのサポートに基づいて、次のように削減できました。
<img
src="/image/sizes:true/company-photo.jpg"
sizes="(min-width: 560px) 251px, 88.43vw"
alt="SAY CHEESY PICKLES."
/>
この例では、/image URL は PHP スクリプトで、その後に mod_rewrite によって書き換えられたパラメータが続きます。画像ファイル名と追加のパラメータを受け取り、バックエンド スクリプトが指定された条件で最適な画像を選択できるようにします。
「これはバックエンドで <picture> と srcset を再実装しているだけではないか?」という疑問が最初に浮かぶかもしれません。
ある意味ではそうですが、重要な違いがあります。アプリケーションがクライアント ヒントを使用してメディア レスポンスを作成する場合、ほとんど(すべてではないにしても)の作業は自動化が容易です。これには、ユーザーの代わりにこの処理を行うサービス(CDN など)が含まれます。一方、HTML ソリューションでは、すべてのユースケースに対応するために新しいマークアップを記述する必要があります。もちろん、マークアップの生成を自動化することはできます。ただし、設計や要件が変更された場合は、将来的に自動化戦略を見直す必要が生じる可能性が高くなります。
クライアント ヒントを使用すると、ロスレスの高解像度画像から開始して、画面とレイアウトのあらゆる組み合わせに最適なサイズに動的に変更できます。srcset では、ブラウザが選択できる候補画像の固定リストを列挙する必要がありますが、このアプローチはより柔軟に対応できます。srcset では、ブラウザに 256w、512w、768w、1024w などの粗いバリエーションのセットを提供する必要がありますが、クライアント ヒントを利用したソリューションでは、大量のマークアップなしで、すべての幅に対応できます。
もちろん、画像選択ロジックを自分で記述する必要はありません。Cloudinary は、w_auto パラメータを使用すると、クライアント ヒントを使用して画像レスポンスを作成します。クライアント ヒントをサポートするブラウザを使用した場合、ユーザーのダウンロード バイト数が 42% 減少することが確認されています。
ただし、注意が必要です。Chrome 67 の PC 版の変更により、クロスオリジン Client Hints のサポートが削除されました。幸いなことに、これらの制限は Chrome のモバイル版には影響しません。また、Feature Policy の作業が完了すると、すべてのプラットフォームで完全に解除されます。
ネットワーク速度が遅いユーザーをサポートする
適応型パフォーマンスとは、クライアント ヒントから得られる情報、特にユーザーのネットワーク接続の現在の状態に関する情報に基づいて、リソースの配信方法を調整できるという考え方です。
Sconnie Timber のサイトについては、ネットワークが遅いときに負荷を軽減する対策を講じています。バックエンド コードで Save-Data、ECT、RTT、Downlink ヘッダーが検査されます。この処理が完了すると、ネットワーク品質スコアが生成されます。このスコアを使用して、ユーザー エクスペリエンスを向上させるために介入すべきかどうかを判断します。このネットワーク スコアは 0~1 の範囲で、0 は可能な限り最悪のネットワーク品質、1 は最高のネットワーク品質です。
最初に、Save-Data が存在するかどうかを確認します。その場合は、ユーザーがエクスペリエンスをより軽く、より速くするために必要なことは何でもしてほしいと望んでいると想定されるため、スコアは 0 に設定されます。
ただし、Save-Data がない場合は、ECT、RTT、Downlink のヒントの値を重み付けして、ネットワーク接続の品質を表すスコアを計算します。ネットワーク スコア生成のソースコードは Github で入手できます。つまり、ネットワーク関連のヒントを何らかの方法で使用すれば、低速ネットワークを使用しているユーザーのエクスペリエンスを向上させることができます。
サイトがクライアント ヒントから提供された情報に適応する場合、全か無かのどちらかというアプローチを採用する必要はありません。送信するリソースをインテリジェントに決定できます。レスポンシブ画像の選択ロジックを変更して、ネットワーク品質が低い場合に、特定のディスプレイに低画質の画像を送信して読み込みパフォーマンスを高速化できます。
この例では、低速ネットワーク上のサイトのパフォーマンスを改善するうえで、クライアント ヒントがどのような影響を与えるかを確認できます。以下は、クライアント ヒントに対応していない低速ネットワーク上のサイトの WebPagetest ウォーターフォールです。
同じサイトで同じ低速接続を使用した場合のウォーターフォールです。ただし今回は、サイトでクライアント ヒントを使用して重要でないページリソースを排除しています。
クライアント ヒントにより、ページの読み込み時間が 45 秒以上から 10 分の 1 以下に短縮されました。このシナリオにおけるクライアント ヒントのメリットは非常に大きく、ネットワークが遅い環境で重要な情報を探しているユーザーにとって大きな助けとなります。
また、クライアント ヒントをサポートしていないブラウザのユーザー エクスペリエンスを損なうことなく、クライアント ヒントを使用することもできます。たとえば、ECT ヒントの値を使用してリソース配信を調整しつつ、サポートされていないブラウザでも完全なエクスペリエンスを提供したい場合は、次のようにデフォルト値にフォールバックを設定できます。
// Set the ECT value to "4g" by default.
$ect = isset($_SERVER["HTTP_ECT"]) ? $_SERVER["HTTP_ECT"] : "4g";
ここで、"4g" は ECT ヘッダーが記述する最高品質のネットワーク接続を表します。$ect を "4g" に初期化すると、クライアント ヒントをサポートしていないブラウザには影響しません。オプトイン最高!
キャッシュに注意しましょう。
HTTP ヘッダーに基づいてレスポンスを変更する場合は、キャッシュがそのリソースの今後のフェッチをどのように処理するかを把握しておく必要があります。Vary ヘッダーは、キャッシュ エントリをリクエスト ヘッダーの値にキー設定するため、ここでは不可欠です。簡単に言うと、特定の HTTP リクエスト ヘッダーに基づいてレスポンスを変更する場合は、次のように Vary にそのヘッダーをリクエストする必要があります。
Vary: DPR, Width
ただし、これには大きな注意点があります。頻繁に変更されるヘッダー(Cookie など)でキャッシュ可能なレスポンスを Vary することは避けるべきです。そのようなリソースは事実上キャッシュ不可になります。このことを踏まえると、RTT や Downlink などのクライアント ヒント ヘッダーで Vary を行うのは避けた方がよいでしょう。これらのヘッダーは、頻繁に変更される可能性がある接続要素であるためです。これらのヘッダーのレスポンスを変更する場合は、ECT ヘッダーのみをキー設定することを検討してください。これにより、キャッシュミスを最小限に抑えることができます。
もちろん、これはレスポンスをキャッシュに保存している場合にのみ適用されます。たとえば、コンテンツが動的な HTML アセットはキャッシュに保存されません。これは、リピート訪問時のユーザー エクスペリエンスを損なう可能性があるためです。このような場合は、必要と思われる根拠に基づいて回答を自由に修正し、Vary を気にしないでください。
サービス ワーカーのクライアントのヒント
コンテンツ ネゴシエーションは、もはやサーバーだけのものではありません。サービス ワーカーはクライアントとサーバー間のプロキシとして機能するため、JavaScript を介してリソースを配信する方法を制御できます。これにはクライアント ヒントが含まれます。サービス ワーカーの fetch イベントでは、event オブジェクトの request.headers.get メソッドを使用して、次のようにリソースのリクエスト ヘッダーを読み取ることができます。
self.addEventListener('fetch', (event) => {
let dpr = event.request.headers.get('DPR');
let viewportWidth = event.request.headers.get('Viewport-Width');
let width = event.request.headers.get('Width');
event.respondWith(
(async function () {
// Do what you will with these hints!
})(),
);
});
オプトインしたクライアント ヒント ヘッダーは、この方法で読み取ることができます。ただし、この情報を取得する方法はこれだけではありません。ネットワーク固有のヒントは、navigator オブジェクトの次の同等の JavaScript プロパティで読み取ることができます。
| クライアント ヒント | JS の同等機能 |
|---|---|
| `ECT` | `navigator.connection.effectiveType` |
| `RTT` | `navigator.connection.rtt` |
| `Save-Data` | `navigator.connection.saveData` |
| `Downlink` | `navigator.connection.downlink` |
| `Device-Memory` | `navigator.deviceMemory` |
これらの API はどこでも利用できるわけではないため、in 演算子を使用して機能チェックを行う必要があります。
if ('connection' in navigator) {
// Work with netinfo API properties in JavaScript!
}
ここから、サーバーで使用するロジックと同様のロジックを使用できます。ただし、クライアント ヒントを使用してコンテンツをネゴシエートするためにサーバーは必要ありません。サービス ワーカーは、ユーザーがオフラインのときにコンテンツを提供できる機能が追加されたため、エクスペリエンスをより高速で復元力のあるものにする力を持っています。
まとめ
クライアント ヒントを使用すると、完全にプログレッシブな方法でユーザー エクスペリエンスを高速化できます。ユーザーのデバイスの機能に基づいてメディアを配信できるため、特に複雑なユースケースでは、<picture> と srcset に依存するよりもレスポンシブ画像を簡単に配信できます。これにより、開発側の時間と労力を削減できるだけでなく、
さらに重要なのは、ネットワーク接続の不良を検出し、送信する内容と送信方法を変更することで、ユーザーのギャップを埋めることができることです。これにより、ネットワークが不安定なユーザーがサイトにアクセスしやすくなります。サービス ワーカーと組み合わせることで、オフラインでも利用できる非常に高速なサイトを作成できます。
クライアント ヒントは Chrome と Chromium ベースのブラウザでのみ利用できますが、クライアント ヒントをサポートしていないブラウザに負担をかけない方法で使用することは可能です。クライアント ヒントを使用して、すべてのユーザーのデバイス機能と接続先のネットワークを認識する、真に包括的で適応性のあるエクスペリエンスを作成することを検討してください。他のブラウザ ベンダーもこれらの価値を認識し、実装の意向を示すことを期待しています。
リソース
- クライアント ヒントによるレスポンシブ画像の自動化
- クライアント ヒントとレスポンシブ画像 - Chrome 67 での変更点
- (クライアント)ヒントを活用しましょう! (スライド)
Save-Dataを使用した高速で軽量なアプリケーションの配信
この記事について貴重なフィードバックと編集をしてくださった Ilya Grigorik 氏、Eric Portis 氏、Jeff Posnick 氏、Yoav Weiss 氏、Estelle Weyl 氏に感謝いたします。