キャッシュを大切に ❤️

ユーザーがサイトを 2 回目に読み込むと HTTP キャッシュが使用されるため、HTTP キャッシュが適切に機能するようにしてください。

この投稿は、Chrome Dev Summit 2020 の拡張コンテンツの一部であるキャッシュを活用する動画に付随するものです。ぜひ、こちらの動画をご覧ください。

ユーザーがサイトを 2 回目に読み込むと、ブラウザは HTTP キャッシュ内のリソースを使用して、読み込みを高速化します。ただし、ウェブのキャッシュに関する標準は 1999 年に定められたものであり、かなり広範に定義されています。CSS や画像などのファイルをネットワークから再度取得するか、キャッシュから読み込むかを判断するのは、やや不確実な作業です。

この記事では、キャッシュに適した最新のデフォルトについて説明します。実際にはキャッシュをまったく使用しないデフォルトです。ただし、これはデフォルトの設定にすぎません。もちろん、単に「オフにする」よりも微妙な違いがあります。ご一読ください。

目標

サイトが 2 回目に読み込まれるときには、次の 2 つの目標があります。

  1. ユーザーが最新バージョンを利用できるようにします。変更を加えた場合は、すぐに反映されるようにします。
  2. ネットワークからできるだけ少ないデータを取得しながら #1 を行う

広い意味では、サイトを再度読み込むときに、クライアントに送信する変更は最小限に抑えるべきです。また、変更を最も効率的に配信するようにサイトを構造化するのは難しい作業です(詳しくは、後述と動画をご覧ください)。

ただし、キャッシュを検討する際には、他の調整要素もあります。たとえば、ユーザーのブラウザ HTTP キャッシュにサイトを長時間保持し、配信にネットワーク リクエストを必要としない場合があります。または、サイトが最新かどうかを確認する前に、サイトを完全にオフラインで配信する Service Worker を作成している場合もあります。これは極端なオプションですが、有効であり、多くのオフライン ファーストのアプリのようなウェブ エクスペリエンスで使用されています。ただし、ウェブはキャッシュのみの極端な状態である必要はなく、ネットワークのみの極端な状態である必要もありません。

背景

ウェブ デベロッパーは、キャッシュが「古くなる」という考えに慣れています。しかし、この問題を解決するためのツールは、ほとんど本能的に知っています。「強制更新」を行うか、シークレット ウィンドウを開くか、ブラウザのデベロッパー ツールを組み合わせてサイトのデータを消去します。

一般のインターネット ユーザーは、そのような余裕はありません。そのため、2 回目の読み込みでユーザーが快適に過ごせるようにすることが Google の主要な目標ですが、不快な思いをしたり、動作が停止したりしないようにすることも非常に重要です。(web.dev/live サイトがクラッシュしそうになったときの様子については、動画をご覧ください)

背景を説明すると、「古いキャッシュ」の一般的な原因は、1999 年当時のデフォルトのキャッシュ設定です。これは Last-Modified ヘッダーに依存します。

ユーザーのブラウザによってさまざまなアセットがキャッシュに保存される時間の長さを示す図
異なる時間に生成されたアセット(グレー)は異なる時間にキャッシュに保存されるため、2 回目の読み込みではキャッシュに保存されたアセットと新しいアセットが組み合わせて取得されます

読み込まれたファイルは、ブラウザが認識する現在の存続期間の 10% 間保持されます。たとえば、index.html が 1 か月前に作成された場合、ブラウザによってさらに 3 日間ほどキャッシュに保存されます。

これは当時は善意に基づくアイデアでしたが、今日のウェブサイトは緊密に統合されているため、このデフォルトの動作では、ウェブサイトの異なるリリース(火曜日のリリースの JS と金曜日のリリースの CSS など)用に設計されたファイルがユーザーに提供される可能性があります。これは、これらのファイルがまったく同じタイミングで更新されていないためです。

明るい小道

キャッシュのデフォルト設定は、実際にはキャッシュをまったく使用せず、CDN を使用してコンテンツをユーザーの近くに配置することです。ユーザーがサイトを読み込むたびに、ネットワークにアクセスして最新の状態かどうかを確認します。このリクエストは、各エンドユーザーに地理的に近い CDN によって提供されるため、レイテンシが低くなります。

ウェブリクエストにこのヘッダーで応答するようにウェブホストを構成できます。

Cache-Control: max-age=0,must-revalidate,public

つまり、このファイルは有効期間がないため、再度使用するにはネットワークから検証する必要があります(検証しないと「推奨」のみとなります)。

この検証プロセスは、転送されるバイト数に比べて比較的低コストです。サイズの大きな画像ファイルが変更されていない場合、ブラウザは小さな 304 レスポンスを受信します。ただし、ユーザーがネットワークにアクセスして確認する必要があるため、レイテンシが発生します。これがこのアプローチの主なデメリットです。これは、先進国で高速接続を使用しているユーザーや、選択した CDN が広範囲にカバーしているユーザーには非常に効果的ですが、モバイル接続が遅いユーザーや、インフラストラクチャが貧弱なユーザーには適していません。

いずれにせよ、これは最新のアプローチであり、一般的な CDN である Netlify ではデフォルトですが、ほとんどの CDN で構成できます。Firebase Hosting の場合、このヘッダーを firebase.json ファイルの hosting セクションに含めることができます。

"headers": [
  // Be sure to put this last, to not override other headers
  {
    "source": "**",
    "headers": [ {
      "key": "Cache-Control",
      "value": "max-age=0,must-revalidate,public"
    }
  }
]

そのため、この値をデフォルトとして使用することをおすすめしますが、あくまでもデフォルトです。デフォルトをアップグレードする方法については、以下をご覧ください。

フィンガープリント付き URL

サイト上で提供されるアセットや画像などのファイル名にファイルの内容のハッシュを含めると、これらのファイルに常に一意のコンテンツが含まれるようにできます。たとえば、ファイル名が sitecode.af12de.js になります。サーバーがこれらのファイルのリクエストに応答する場合は、このヘッダーで構成して、エンドユーザーのブラウザに長時間キャッシュに保存するよう安全に指示できます。

Cache-Control: max-age=31536000,immutable

この値は 1 年の秒数です。仕様によると、これは事実上「永遠」と同じです。

重要な点として、これらのハッシュを手動で生成しないでください。手作業では負担が大きすぎます。この作業には、Webpack や Rollup などのツールを使用できます。詳しくは、ツールレポートをご覧ください。

なお、フィンガープリント URL のメリットを享受できるのは JavaScript だけではありません。アイコン、CSS、その他の不変のデータファイルなどのアセットも、この方法で名前を付けることができます。(また、上記の動画で、サイトの変更時にコードの量を減らすことができるコード分割について学びましょう)。

サイトのキャッシュ保存方法にかかわらず、このような指紋付きファイルは、構築するサイトにとって非常に価値があります。ほとんどのサイトは、リリースごとに変更されることはありません。

もちろん、ユーザー向けの「わかりやすい」ページの名前をこのように変更することはできません。index.html ファイルを index.abcd12.html に変更するなど、現実的ではありません。サイトを読み込むたびに新しい URL に移動するようユーザーに伝えることはできません。これらの「わかりやすい」URL は、この方法で名前を変更してキャッシュに保存することはできません。そこで、妥協案を検討します。

中間地点

キャッシュ保存に関しては、中間的な方法も考えられます。キャッシュをキャッシュに保存しないか、キャッシュに保存するかの 2 つの極端なオプションを紹介しました。また、前述の「フレンドリー」な URL など、しばらくキャッシュに保存しておきたいファイルもあります。

これらの「フレンドリー」な URL とその HTML をキャッシュに保存する場合は、それらに含まれる依存関係、キャッシュに保存する方法、URL を一定期間キャッシュに保存することで発生する影響について検討することをおすすめします。次のような画像を含む HTML ページを見てみましょう。

<img src="/images/foo.jpeg" loading="lazy" />

この遅延読み込み画像を削除または変更してサイトを更新または変更した場合、HTML のキャッシュ バージョンを表示しているユーザーには、正しくない画像が表示されたり、画像が表示されなかったりすることがあります。これは、サイトに再度アクセスしたときに、元の /images/foo.jpeg がキャッシュに残っているためです。

注意深く使用すれば、影響を受けない可能性があります。ただし、エンドユーザーがキャッシュに保存すると、サイトはサーバーにのみ存在しなくなることを覚えておくことが重要です。むしろ、エンドユーザーのブラウザのキャッシュ内に断片として存在する可能性があります。

一般に、キャッシュに関するほとんどのガイドでは、1 時間、数時間など、キャッシュに保存する時間について説明しています。このタイプのキャッシュを設定するには、次のようなヘッダーを使用します(3,600 秒、つまり 1 時間キャッシュに保存されます)。

Cache-Control: max-age=3600,immutable,public

最後にもう 1 つ。ニュース記事など、ユーザーが通常 1 回しかアクセスしないタイムリーなコンテンツを作成する場合は、キャッシュに保存しないことをおすすめします。上記の適切なデフォルト設定を使用してください。ニュース記事や最新の出来事に関する重要な更新など、最新の優れたコンテンツを常に表示したいというユーザーの希望よりも、キャッシュの価値を過大評価していることが多いと思います。

HTML 以外のオプション

HTML 以外にも、中間的なファイルには次のようなオプションがあります。

  • 通常は、他のアセットに影響を与えないアセットを探します。

    • たとえば、HTML のレンダリング方法が変更されるため、CSS は使用しないでください。
  • タイムリーな記事の一部として使用される大きな画像

    • ユーザーが 1 つの記事にアクセスする回数は数回程度であるため、写真やヒーロー画像を永久にキャッシュに保存してストレージを浪費しないでください。
  • 自体に存続期間があるものを表すアセット

    • 天気に関する JSON データは 1 時間ごとにのみ公開される可能性があるため、前の結果を 1 時間キャッシュに保存できます。この期間は結果が変更されません。
    • オープンソース プロジェクトのビルドはレート制限されている可能性があるため、ステータスが変更される可能性があるまでビルド ステータス イメージをキャッシュに保存します。

概要

ユーザーがサイトを 2 回目に読み込むとき、ユーザーはすでに信頼を表明しています。つまり、ユーザーはサイトに戻って、提供されている商品やサービスをさらに利用したいと考えています。現時点では、読み込み時間を短縮するだけが目的ではありません。高速で最新のブラウジング エクスペリエンスを実現するために必要な作業のみをブラウザが行うようにする方法がいくつかあります。

キャッシュはウェブでは新しい概念ではありませんが、適切なデフォルト設定が必要になる場合があります。デフォルト設定の使用を検討し、必要に応じてより優れたキャッシュ戦略を強くオプトインしてください。今後ともよろしくお願いいたします。

関連情報

HTTP キャッシュに関する一般的なガイドについては、HTTP キャッシュで不要なネットワーク リクエストを防ぐをご覧ください。