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

ネットワーク経由でリソースを取得するのは時間がかかり、費用もかかります。

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

不要なネットワーク リクエストを回避するにはどうすればよいですか?ブラウザの HTTP キャッシュが最初の防衛線となります。最も強力な方法や柔軟な方法とは限らず、キャッシュに保存されたレスポンスの存続期間を制御する範囲も限られますが、効果が高く、すべてのブラウザでサポートされており、手間もかかりません。

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

ブラウザの互換性

実際には、HTTP Cache という単一の API はありません。これは、ウェブ プラットフォーム API のコレクションの一般的な名前です。これらの API は、すべてのブラウザでサポートされています。

Cache-Control

Browser Support

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 1.
  • Safari: 1.

Source

ETag

Browser Support

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 1.
  • Safari: 1.

Source

Last-Modified

Browser Support

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 1.
  • Safari: 1.

Source

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 の長期保存キャッシュ

たとえば、サーバーがブラウザに 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 に対してはどのような対応をすればよいでしょうか。

これは、敗北を認める必要があるシナリオの一つです。HTTP キャッシュだけでは、ネットワークを完全に回避できるほど強力ではありません。(ご安心ください。すぐにサービス ワーカーについて学び、戦況を有利に進めるためのサポートを得ることができます)。ただし、ネットワーク リクエストを可能な限り迅速かつ効率的に行うための手順がいくつかあります。

次の 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 を使用することをおすすめします。

最初のフェッチから 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 のキャッシュのベスト プラクティスと max-age の落とし穴ガイドをご覧ください。

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

付録: その他のヒント

時間がある場合は、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 秒 x 10 分)キャッシュに保存できます。
public, max-age=31536000 レスポンスは任意のキャッシュに 1 年間保存できます。
no-store レスポンスはキャッシュに保存できません。リクエストごとに完全に取得する必要があります。