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

ネットワーク経由でのリソースの取得は低速であり、コストもかかります。

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

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

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

ブラウザの互換性

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

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 によって判断されて最新でなくなるか、その他の理由(ユーザーがブラウザのキャッシュをクリアした場合など)でキャッシュから削除されるまで、使用されます。その結果、ページの作成時にユーザーが異なるバージョンのファイルを使用することになります。リソースをフェッチしたばかりのユーザーは新しいバージョンを使用し、以前の(ただし有効な)コピーをキャッシュに保存したユーザーは古いバージョンのレスポンスを使用します。では、クライアントサイドのキャッシュ保存とクイック アップデートという 2 つの利点を活かすにはどうすればよいでしょうか。リソースの 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 であることを覚えておく必要がある場合、誰もウェブアプリを使用することはないため、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 の例をご覧ください。

それに加えて、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 use using the 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 の Caching best practices & max-agegotchas ガイドをご覧ください。

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

付録: その他のヒント

時間があれば、さらに以下の方法で 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 レスポンスをキャッシュに保存することはできず、リクエストごとに完全に取得する必要があります。