HTTP キャッシュを使用して不要なネットワーク リクエストを防止する

ネットワーク経由でのリソースの取得は、時間とコストがかかります。

  • レスポンスのサイズが大きいと、ブラウザとサーバー間で多数のラウンドトリップが必要になります。
  • ページは、すべての重要なリソースのダウンロードが完了するまで読み込まれません。
  • あるユーザーがモバイルデータ プランに制限のあるサイトにアクセスしている場合、不要なネットワーク リクエストが発生するたびにお金の無駄遣いになります。

不要なネットワーク リクエストを回避するにはどうすればよいでしょうか。防御の最前線は、ブラウザの HTTP キャッシュです。この方法は、必ずしも最も強力で柔軟な方法というわけではなく、キャッシュに保存されたレスポンスの存続期間を制御するのは限定的ですが、効果的で、すべてのブラウザでサポートされるため、多くの作業は必要ありません。

このガイドでは、効果的な HTTP キャッシュ実装の基本について説明します。

ブラウザの互換性

実際には、HTTP キャッシュと呼ばれる単一の API はありません。ウェブ プラットフォーム API のコレクションの総称です。これらの API は、すべてのブラウザでサポートされています。

Cache-Control

対応ブラウザ

  • 正しい
  • 12
  • 正しい
  • 正しい

ソース

ETag

対応ブラウザ

  • 正しい
  • 12
  • 正しい
  • 正しい

ソース

Last-Modified

対応ブラウザ

  • 正しい
  • 12
  • 正しい
  • 正しい

ソース

HTTP キャッシュの仕組み

ブラウザが実行するすべての HTTP リクエストは、まずブラウザ キャッシュにルーティングされ、リクエストの実行に使用できる有効なキャッシュ レスポンスがあるかどうかが確認されます。一致する場合、レスポンスがキャッシュから読み取られるため、ネットワーク レイテンシと転送によって発生するデータコストの両方を排除できます。

HTTP キャッシュの動作は、リクエスト ヘッダーレスポンス ヘッダーの組み合わせによって制御されます。リクエスト ヘッダーを決めるウェブ アプリケーションのコードとウェブサーバーの構成(レスポンス ヘッダーを決定)の両方を、ユーザーが管理できるようにすることが理想的です。

コンセプトの概要について詳しくは、MDN の HTTP キャッシュ保存の記事をご覧ください。

リクエスト ヘッダー: デフォルトのまま(通常)

ウェブアプリの送信リクエストには、多くの重要なヘッダーを含める必要がありますが、リクエストを行う際、ほとんどの場合、ブラウザがユーザーに代わってヘッダーを設定します。鮮度の確認に影響するリクエスト ヘッダー(If-None-MatchIf-Modified-Since など)は、ブラウザで認識されている HTTP キャッシュの現在の値に基づいて表示されます。

これは朗報です。HTML に <img src="my-image.png"> などのタグをそのまま追加すれば、手間をかけずにブラウザが自動的に HTTP キャッシュを処理します。

レスポンス ヘッダー: ウェブサーバーを構成する

HTTP キャッシュ設定で最も重要な部分は、ウェブサーバーが各送信レスポンスに追加するヘッダーです。以下のヘッダーはすべて、効果的なキャッシュ動作の要因となります。

  • Cache-Control。サーバーは Cache-Control ディレクティブを返して、ブラウザやその他の中間キャッシュが個々のレスポンスをキャッシュに保存する方法と期間を指定できます。
  • ETag。ブラウザは、キャッシュに保存されている期限切れのレスポンスを見つけると、小さなトークン(通常はファイルの内容のハッシュ)をサーバーに送信して、ファイルが変更されたかどうかを確認できます。サーバーから同じトークンが返された場合、ファイルは同じものであるため、再ダウンロードする必要はありません。
  • Last-Modified。このヘッダーは ETag と同じ目的を果たしますが、コンテンツ ベースの戦略(ETag)とは対照的に、時間ベースの戦略を使用してリソースが変更されたかどうかを判断します。

ウェブサーバーによっては、これらのヘッダーをデフォルトで設定するための組み込みサポートが組み込まれている場合や、明示的に構成しない限り、ヘッダーが完全に省略される場合もあります。ヘッダーの設定方法の詳細は、使用するウェブサーバーによって大きく異なります。最も正確な情報を得るには、サーバーのドキュメントをご覧ください。

ここでは、一般的なウェブサーバーの構成手順を説明します。

Cache-Control レスポンス ヘッダーを省略しても、HTTP キャッシュは無効になりません。その代わり、ブラウザは特定の種類のコンテンツに対してどのようなキャッシュ動作が最も理にかなっているかを実質的に推測します。このオファーよりも細かい制御が必要な場合は、時間をかけてレスポンス ヘッダーを設定してください。

どのレスポンス ヘッダー値を使用すればよいですか。

ウェブサーバーのレスポンス ヘッダーを構成する際には、考慮すべき重要なシナリオが 2 つあります。

バージョニングされた URL の長期保存キャッシュ

バージョニングされた URL がキャッシュ戦略にどのように役立つか
バージョニングされた URL は、キャッシュされたレスポンスを簡単に無効にできるため、おすすめの方法です。

たとえば、ブラウザから CSS ファイルを 1 年間(Cache-Control: max-age=31536000)キャッシュに保存するようサーバーに指示したものの、デザイナーが緊急の更新を作成したとします。この更新はすぐにデプロイする必要があります。キャッシュされたファイルの「古い」コピーを更新するようブラウザに通知するには、どうすればよいですか。少なくとも、リソースの URL を変更せずにはいけません。

ブラウザがレスポンスをキャッシュに保存した後、そのキャッシュ バージョンは、新鮮でなくなるまで(max-age または expires によって判別される)か、その他の理由(ユーザーがブラウザのキャッシュをクリアした場合など)でキャッシュから削除されるまで使用されます。その結果、ページが作成されたときに、ファイルのバージョンが異なるユーザーになる可能性があります。たとえば、リソースを取得したばかりのユーザーは新しいバージョンを使用し、以前の(ただし有効な)コピーをキャッシュに保存したユーザーは古いバージョンのレスポンスを使用します。

クライアント側のキャッシュと迅速な更新という両方の長所を活かすにはどうすればよいでしょうか。リソースの URL を変更し、コンテンツが変更されるたびにユーザーに新しいレスポンスをダウンロードさせる。通常、これを行うには、ファイルのフィンガープリントまたはバージョン番号をファイル名に埋め込みます(例: style.x234dff.css)。

フィンガープリント」またはバージョニング情報を含む、内容が変更される意図のない URL のリクエストに応答する場合は、レスポンスに Cache-Control: max-age=31536000 を追加します。

この値を設定すると、ブラウザでは今後 1 年間(サポートされている最大値の 31,536,000 秒)に同じ URL を読み込む必要がある場合、ウェブサーバーに対してネットワーク リクエストを行わなくても、HTTP キャッシュ内の値をすぐに使用することができます。すばらしい。ネットワークを使わないと得られる信頼性とスピードがすぐに得られるということです。

webpack などのビルドツールを使用すると、ハッシュ フィンガープリントをアセット URL に割り当てるプロセスを自動化できます。

バージョン管理されていない URL のサーバー再検証

残念ながら、読み込むすべての URL がバージョン管理されているわけではありません。ウェブアプリをデプロイする前にビルドステップを含めることができないため、アセット URL にハッシュを追加できない場合もあります。また、すべてのウェブ アプリケーションには HTML ファイルが必要です。アクセスする URL が https://example.com/index.34def12.html であることを覚えておく必要がある場合は、ウェブアプリの使用に煩わされることはないため、これらのファイルにバージョニング情報を含めることはほとんどありません。では、それらの URL に対してどうすればよいでしょうか。

これは失敗を認める必要があるシナリオの 1 つです。ネットワークを完全に回避するには、HTTP キャッシュだけでは不十分です。(心配は無用です。Service Worker については後ほど説明します。Service Worker は、戦いを有利に進めるために必要なサポートを提供します)。ただし、ネットワーク リクエストを可能な限り迅速かつ効率的に行うために実行できるステップがいくつかあります。

次の Cache-Control 値は、バージョニングされていない URL をキャッシュする場所と方法を微調整するのに役立ちます。

  • no-cache。これにより、キャッシュされた URL を使用する前に、毎回サーバーで再検証を行うようブラウザに指示します。
  • no-store。これにより、ブラウザと他の中間キャッシュ(CDN など)にファイルのどのバージョンも保存されなくなります。
  • private。ブラウザはファイルをキャッシュに保存できますが、中間キャッシュはキャッシュに保存できません。
  • public。レスポンスは任意のキャッシュに保存できます。

使用する Cache-Control 値を決定するプロセスを可視化するには、付録: Cache-Control フローチャートをご覧ください。Cache-Control には、ディレクティブのカンマ区切りのリストを指定することもできます。付録: Cache-Control の例をご覧ください。

ETag または Last-Modified の設定も役立ちます。レスポンス ヘッダーで説明したように、ETagLast-Modified はどちらも同じ目的を果たし、期限切れのキャッシュ ファイルをブラウザが再ダウンロードする必要があるかどうかを判断します。より正確な ETag の使用をおすすめします。

ETag の例

最初の取得から 120 秒が経過し、ブラウザが同じリソースに対する新しいリクエストを開始したとします。まず、ブラウザは HTTP キャッシュをチェックして、前のレスポンスを見つけます。残念ながら、レスポンスの有効期限が切れているため、ブラウザは以前のレスポンスを使用できません。この時点で、ブラウザは新しいリクエストをディスパッチして、新しい完全なレスポンスを取得できます。ただし、リソースが変更されていなければ、キャッシュにすでに存在するものと同じ情報をダウンロードする理由がないため、非効率的です。

この問題は、ETag ヘッダーで指定された検証トークンが解決できるように設計されています。サーバーは任意のトークンを生成して返します。これは通常、ファイルの内容のハッシュまたはその他のフィンガープリントです。ブラウザはフィンガープリントの生成方法を知っている必要はなく、次のリクエストでサーバーに送信するだけで済みます。フィンガープリントが同じ場合、リソースは変更されていないため、ブラウザはダウンロードをスキップできます。

ETag または Last-Modified を設定すると、リクエスト ヘッダーに記載されている If-Modified-Since または If-None-Match リクエスト ヘッダーを、再検証リクエストからトリガーできるため、効率性が大幅に向上します。

適切に構成されたウェブサーバーは、これらの受信リクエスト ヘッダーを認識することで、ブラウザにすでに HTTP キャッシュに保存されているリソースのバージョンがウェブサーバー上の最新バージョンと一致するかどうかを確認できます。一致するものがある場合、サーバーは 304 Not Modified HTTP レスポンスで応答できます。これは、「すでに持っているものを使い続ける」と同等です。このタイプのレスポンスを送信する際、転送するデータはほとんどないため、通常はリクエストされた実際のリソースのコピーを実際に返送するよりもはるかに高速です。

クライアントがリソースをリクエストし、サーバーが 304 ヘッダーで応答している様子を可視化した図。
ブラウザはサーバーに /file をリクエストし、If-None-Match ヘッダーを含めて、サーバー上のファイルの ETag がブラウザの If-None-Match 値と一致しない場合にのみファイル全体を返すようにサーバーに指示します。この場合、2 つの値が一致したため、サーバーはファイルをキャッシュに保存する期間(Cache-Control: max-age=120)を指示する 304 Not Modified レスポンスを返します。

まとめ

HTTP キャッシュを使用すると、不要なネットワーク リクエストを減らせるため、読み込みパフォーマンスの改善に効果的です。すべてのブラウザでサポートされているため、それほど手間のかからない設定です。

まずは、次の Cache-Control 構成をおすすめします。

  • Cache-Control: no-cache: 使用前にサーバーで再検証する必要があるリソース。
  • Cache-Control: no-store: キャッシュに保存してはならないリソース。
  • バージョニングされたリソースの場合は Cache-Control: max-age=31536000

また、ETag ヘッダーまたは Last-Modified ヘッダーは、期限切れのキャッシュ リソースをより効率的に再検証するのに役立ちます。

さらに詳しく

Cache-Control ヘッダーの基本的な使用方法については、Jake Archibald のキャッシュのベスト プラクティスと最大年齢の注意点のガイドをご覧ください。

リピーターのためにキャッシュの使用を最適化する方法については、キャッシュの活用に関する記事をご覧ください。

付録: その他のヒント

時間があれば、以下の方法で HTTP キャッシュの使用を最適化できます。

  • 一貫した URL を使用する。同じコンテンツを異なる URL で提供すると、そのコンテンツは複数回取得、保存されます。
  • チャーンを最小限に抑える。リソースの一部(CSS ファイルなど)は頻繁に更新され、ファイルの残りの部分(ライブラリ コードなど)は更新されない場合、頻繁に更新されるコードを別のファイルに分割し、頻繁に更新されるコードには短期のキャッシュ戦略を、頻繁に変更されないコードには長期のキャッシュ戦略を使用することを検討してください。
  • Cache-Control ポリシーである程度の未更新が許容される場合は、新しい stale-while-revalidate ディレクティブを確認してください。

付録: Cache-Control フローチャート

フローチャート
Cache-Control ヘッダーの設定に関する決定プロセス。

付録: Cache-Control の例

Cache-Control 解説
max-age=86400 レスポンスは、最大 1 日間(60 秒 x 60 分 x 24 時間)ブラウザと中間キャッシュでキャッシュに保存できます。
private, max-age=600 レスポンスは、最大 10 分間(60 秒 × 10 分間)ブラウザでキャッシュに保存できます(中間キャッシュは使用できません)。
public, max-age=31536000 レスポンスは、1 年間、任意のキャッシュに保存できます。
no-store レスポンスはキャッシュに保存できません。リクエストごとに完全にフェッチする必要があります。