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

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

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

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

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

ブラウザの互換性

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

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 秒、サポートされている最大値は 31,536, 000 秒)に同じ URL を読み込む必要があるときに、ウェブサーバーにネットワーク リクエストを送信することなく、すぐに HTTP キャッシュ内の値を読み込むことがブラウザに通知されます。これで、ネットワークを回避したことで得られる信頼性とスピードがすぐに得られました。

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

バージョニングされていない URL に対するサーバーの再検証

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

これは、負けを認める必要があるシナリオの一つです。ネットワークを完全に回避するには HTTP キャッシュだけでは不十分です(ご安心ください。Service Worker については後ほど説明します。Service Worker は、有利な立場に立ち返るために必要なサポートを提供します。)ただし、ネットワーク リクエストを可能な限り迅速かつ効率的に実行するために実行できる手順がいくつかあります。

次の Cache-Control 値を使用すると、バージョニングされていない URL をキャッシュに保存する場所と方法を微調整できます。

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

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

それに加えて、2 つの追加のレスポンス ヘッダー(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 値と一致しない場合にのみファイル全体を返すようにサーバーに指示します。この場合、2 つの値が一致しているため、サーバーは 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 解説
max-age=86400 レスポンスは、ブラウザと中間キャッシュで最大 1 日(60 秒 x 60 分 x 24 時間)キャッシュに保存できます。
private, max-age=600 レスポンスは最大 10 分間(60 秒 x 10 分)ブラウザによってキャッシュに保存されます(中間キャッシュはキャッシュに保存できません)。
public, max-age=31536000 レスポンスは任意のキャッシュに保存でき、1 年間保存されます。
no-store レスポンスをキャッシュに保存することは許可されていないため、リクエストごとに完全に取得する必要があります。