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

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

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

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

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

ブラウザの互換性

実際には、HTTP キャッシュと呼ばれる単一の 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-Match や If-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 を設定することも有効です。レスポンス ヘッダーで説明したように、ETag と Last-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 つの値が一致したため、サーバーは 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 レスポンスはキャッシュに保存できず、リクエストごとに完全に取得する必要があります。