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

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

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

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

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

ブラウザの互換性

HTTP キャッシュは、すべてのブラウザでサポートされているウェブ プラットフォーム API の総称です。

Cache-Control

対応ブラウザ

  • True
  • 12
  • True
  • True

ソース

ETag

対応ブラウザ

  • True
  • 12
  • True
  • True

ソース

Last-Modified

対応ブラウザ

  • True
  • 12
  • True
  • True

ソース

HTTP キャッシュの仕組み

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

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

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

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

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

つまり、<img src="my-image.png"> などのタグを HTML に引き続き含めることができ、ブラウザは余分な手間をかけずに 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 にハッシュを追加できない場合もあります。また、アクセスする URL が https://example.com/index.34def12.html であることを覚える必要があったウェブアプリを気にかける人がいないため、すべてのウェブ アプリケーションには、バージョニング情報はほとんど含まれない HTML ファイルが必要になります。では、そのような URL に対して何ができるでしょうか。

HTTP キャッシュだけでは、ネットワークを完全に回避するには不十分です。(追加のサポートを提供する 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 レスポンスで応答できます。これは、「Hey, keep using what you have already's!」と同等です。このタイプのレスポンスを送信する場合、転送するデータはほとんどないため、通常は、リクエストされている実際のリソースのコピーを実際に返すよりもはるかに高速です。

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

まとめ

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 レスポンスはキャッシュに保存できないため、リクエストごとに完全に取得する必要があります。