ブラウザキャッシュは何日残るのか
2026年9月25日
著者: 竹洞 陽一郎
主なポイント
- max-ageは「いつまで再確認なしで使ってよいか」を決めるだけで「いつまで保存するか」は決めず、RFC 9111はキャッシュに保存の義務を課していないため、max-age=31536000を指定しても365日間保持されるわけではありません。
- Facebookの2015年の調査では、平均して44.6%のユーザーが空のキャッシュの状態でアクセスしており、モバイルではリクエストの50%が、キャッシュの古さが12時間以内の状態で行われていました。
- Chrome、Firefox、Safariのいずれも、キャッシュ容量は空き容量から計算されてすべてのサイトで共有され、容量を超えると追い出されますが、追い出しの基準に有効期限は含まれません。
- SafariのITPによる7日間のストレージ削除はHTTPキャッシュには適用されませんが、サードパーティのHTTPキャッシュはサイトごとに分割されるため、静的ファイルはサイトと同じ登録可能ドメイン(例:static.example.jp)で配信します。
- アプリ内ブラウザ(WebView)のキャッシュはアプリごとに分かれており、SNSアプリからの流入はリピーターでも初回訪問と同じ状態になりやすいため、キャッシュのない初回表示の性能を確保し、キャッシュの実効性はCDNのエッジサーバーで担保します。
「1年キャッシュさせているから、リピーターは速いはず」は本当か
静的ファイルに Cache-Control: max-age=31536000 を付けているサイトは多いです。
31,536,000秒は365日です。
この指定を見て、「リピーターのブラウザには1年間キャッシュが残っているから、2回目以降の表示は速いはずだ」と考えている方は少なくありません。
しかし、これは事実ではありません。
max-ageは「いつまで再確認なしで使ってよいか」を決めるだけで、「いつまで保存するか」を決めるものではありません。
実際の保持期間は、ブラウザのキャッシュ容量、追い出し(eviction)の処理、ユーザーの操作、OSの状態で決まります。
特にモバイルでは、期待するよりはるかに短い期間で消えています。
この記事では、HTTPの仕様、大規模サイトの実測データ、主要ブラウザの実装の3つから、ブラウザキャッシュの実際の保持期間を説明します。
「では、max-ageを何秒にすべきか」は続きの記事で、「自分のサイトで実測するには」は3本目の記事で扱います。
仕様上の位置付け:RFC 9111はキャッシュの保存を義務付けていない
HTTPのキャッシュの仕様は、RFC 9111(HTTP Caching)です。
RFC 7234をObsoleteにした現行の仕様で、2026年9月25日時点でこれをObsoleteにするRFCはありません。
RFC 9111のSection 2では、キャッシュはHTTPにおいて完全にOPTIONAL(任意)な機能であると定義されています。
キャッシュに関する要件は、再利用すべきでないレスポンスを保存したり不適切に再利用したりすることを防ぐためのものであり、特定のレスポンスを常に保存・再利用することを義務付けるものではない、とされています。
つまり、サーバーがどれだけ長い有効期限を指定しても、ブラウザにはそれを保存し続ける義務がありません。
max-ageの意味は、Section 5.2.2.1で定義されています。
max-ageレスポンスディレクティブは、レスポンスの経過時間(age)が指定秒数を超えたらstale(古い)とみなす、という意味です。
「いつまでfresh(新鮮)か」を決めるだけで、「いつまで保存するか」は決めていません。
さらに、Section 5.3(Expires)には、長い有効期限について直接的な記述があります。
長い鮮度期間はもはや禁止されていないものの、極端に大きな値は問題を起こすことがあり、多くのキャッシュはそれよりはるかに早くレスポンスを追い出す、と書かれています。
これは規範的な要件(MUSTやSHOULD)ではなく、実装の実態を説明した記述です。
仕様の側が、「1年と書いても、キャッシュはそれより早く消す」と明言しているということです。
実測データ:半数近くのユーザーは空のキャッシュで訪問している
大規模サイトが、実際のユーザーのキャッシュの状態を計測した調査が2つあります。
Yahoo!の調査(2007年)
Yahoo!のパフォーマンス研究チームが2007年に公開した「Browser Cache Usage - Exposed!」では、ページビューの約20%が空のキャッシュの状態で行われ、ユーザーの40〜60%が空のキャッシュを経験していると報告されました。
この結果は当時の業界に衝撃を与え、Steve Souders氏の著書などを通じて広く知られるようになりました。
Facebookの調査(2015年)
Facebookは2015年に、Yahoo!の調査を再現する形で、自社のユーザーのキャッシュの状態を計測しました(Web performance: Cache efficiency exercise)。
主な結果は次のとおりです。
- デスクトップのリクエストの24.8%、モバイルのリクエストの26.9%が、キャッシュされているはずの画像を持っていなかった
- 平均して44.6%のユーザーが、空のキャッシュの状態でアクセスしていた
- モバイルでは、リクエストの50%が、キャッシュの古さが12時間以内の状態で行われていた
- デスクトップでは、リクエストの42%が、キャッシュの古さが47時間以内の状態で行われていた
Facebookは、ユーザーが毎日のように訪問するサイトです。
そのFacebookでさえ、モバイルでは半数のリクエストが12時間以内のキャッシュしか持っていませんでした(この値は、キャッシュの消失と訪問頻度の両方を反映したものです)。
訪問頻度が月に数回というサイトであれば、リピーターのブラウザにキャッシュが残っている期待は、さらに小さくなります。
2007年と2015年で、空のキャッシュのユーザーの割合がほとんど変わっていない点も重要です。
端末のストレージは8年間で大きく増えましたが、空のキャッシュの割合は下がっていません。
Webページのサイズが同じ期間に大きく増えたことが理由の1つだと私は考えていますが、Facebookの調査はその因果関係までは示していません。
なお、2015年以降、これに相当する規模の公開調査は見当たりません。
現在のブラウザやストレージの事情の下で同じ割合になるかは、公開データからは分からないので、自分のサイトについては実測で確かめる必要があります(方法はブラウザキャッシュのヒット率を生存時間解析で実測するで説明しています)。
各ブラウザの実装
Chrome(Chromium)
Chromeのディスクキャッシュの上限サイズは固定値ではなく、端末の空き容量から計算されます。
Chromiumのソースコード(net/disk_cache/cache_util.cc)では、基準値kDefaultCacheSizeは80MiBと定義されています。
空き容量に応じて、空き容量の80%、基準値そのもの、空き容量の10%、基準値の2.5倍、空き容量の1%と段階的に計算され、Windows以外のプラットフォームではHTTPのディスクキャッシュについて基準値を4倍に拡大したうえで、その4倍を上限としています(2026年9月25日時点のmainブランチ)。
空き容量が十分にあれば数百MBから1GB超まで拡大されますが、この容量を、ユーザーが訪問するすべてのサイトのリソースで共有しています。
容量を超えると、「最終利用からの経過時間 × エントリのサイズ」が大きいエントリから追い出されます(net/disk_cache/simple/simple_index.cc。ソースコードはmax-ageは何秒にすべきかで示します)。
同じ時間放置されたエントリなら、大きいファイルのほうが先に追い出されるということです。
Chromeのリロード時の挙動は、2017年に変更されています。
Chromiumの公式ブログ(Reload, reloaded: faster and leaner page reloads)によると、リロード時にはメインリソース(HTML)だけを再検証し(再検証とは、キャッシュを使う前にサーバーへ更新の有無を問い合わせることです。更新がなければ304 Not Modifiedが返り、本文は転送されません)、サブリソース(CSS、JavaScript、画像など)は通常の読み込みと同じ扱いにする方式に変わりました。
この変更でリロードは最大28%速くなり、Facebookの計測では再検証リクエストが60%減ったとされています。
ここで、Cache-Controlのimmutableディレクティブについて説明しておきます。
immutableは、RFC 8246(HTTP Immutable Responses、2017年9月、Proposed Standard)で定義された拡張ディレクティブで、「このレスポンスは有効期限内に更新されることはない」とサーバーが宣言するものです。
RFC 8246のSection 2では、immutableが付いたレスポンスに対して、クライアントは有効期限内には条件付きリクエスト(再検証)を発行すべきでない(SHOULD NOT)とされています。
ユーザーの再読み込み操作で、まだ有効期限内のサブリソースまで再検証されてしまう無駄をなくすために作られました。
Section 3では、HTTPSのような認証されたコンテキストでないリソースについては、クライアントはimmutableを無視すべき(SHOULD)ともされています。
Chromeでのimmutableの扱いは、次のとおりです。
Chromiumのソースコード(net/base/features.ccのkCacheControlImmutable)にimmutableを解釈する実装が入っていますが、2026年9月25日時点のmainブランチでは既定で無効です。
ただし、上記のリロード時の挙動により、Chromeではimmutableがなくてもリロード時にサブリソースの再検証は基本的に発生しません。
immutableの有無で差が出るのは、主に次に述べるFirefoxです。
Firefox
Firefoxのキャッシュ実装(cache2)は、Mozilla公式の開発者向け文書(Firefox Source Docs: HTTP Cache)に説明があります。
追い出しの基準はfrecency(frequency + recency)です。
利用頻度と最終利用時刻を組み合わせた値で、アクセスのたびに再計算されます。
ディスク容量の上限を超えたときには、frecencyが最も低いエントリから追い出されます。
減衰の半減期は browser.cache.frecency_half_life_hours で制御され、既定値は6時間です。
この仕組みから、訪問頻度の低いサイトのリソースほどfrecencyが下がり、追い出し候補になりやすいと解釈できます(仕様書の記述ではなく、実装からの解釈です)。
上限サイズは、Chromeと同じく空き容量から計算されます。
既定値は約1GBという情報がありますが、これは公式文書ではなくサポートフォーラムの回答で、現行版での値は確認していません。
リロード時の挙動はChromeと異なります。
Firefoxはリロードボタンの従来の挙動を維持しているため、リロード時にサブリソースも再検証されます。
ただし、immutableが付いたリソースは再検証されません。
immutableの効果が実際に現れるのは、主にFirefoxのこの場面です。
Safari(WebKit)
WebKitのNetworkCacheも、空き容量からキャッシュ容量を計算します。
二次資料で引用されているソースコードでは、主ブラウザ用の設定の場合、空き容量に応じて100MBから1GBまで段階的に容量が決まるとされています。
この値は二次資料経由で、現行iOSのSafariに同じ値が適用されているかは確認していません。
iOSでは、端末のストレージが逼迫した際にOSがアプリのキャッシュを削除することがあります。
SafariのHTTPキャッシュがどの条件で削除されるかについて、Appleの公式文書で明記されたものは確認できていません。
SafariのITPはHTTPキャッシュに何をするか
Safariには、ITP(Intelligent Tracking Prevention)によるストレージの削除があります。
WebKitの公式文書(Tracking Prevention in WebKit)によると、ITPは、7日間のブラウザ利用中にユーザー操作(クリック、タップ、キーボード入力)がなかったサイトについて、スクリプトから書き込めるストレージをすべて削除します。
対象として列挙されているのは、IndexedDB、LocalStorage、Media keys、SessionStorage、Service Workerの登録とキャッシュの5つです。
HTTPキャッシュはこの一覧に含まれていません。
したがって、通常のファーストパーティのサイトのCSSや画像のHTTPキャッシュが、この7日間のルールで消えることはありません。
一方で、サードパーティのコンテンツのHTTPキャッシュには、別の扱いがあります。
WebKitの文書では、アドレスバーに表示されているサイトをファーストパーティ、そのページが読み込む別サイトのリソースをサードパーティと呼びます。
サイトの単位は登録可能ドメイン(example.jp や example.co.jp のように、登録の単位となるドメインと、そのサブドメイン全体)です。
- サードパーティのHTTPキャッシュは、ファーストパーティのサイトごとに分割されます(Partitioned Third-Party HTTP Cache)
- ITPが「クロスサイト追跡の能力あり」と分類したドメインのキャッシュエントリは、7日後のヒット時に再取得して内容を照合し、一致しなければ破棄されます(Verified Partitioned Cache)
- 分類されたドメインで、30日間のブラウザ利用中にファーストパーティとしてのユーザー操作がなければ、すべてのWebサイトデータが削除されます。この「Webサイトデータ」にHTTPキャッシュが含まれるかどうかは、文書に明記されていません
分類の基準の1つは、「多数のファーストパーティサイトの下でサードパーティのサブリソースとして現れること」です。
これは、公開CDN(cdnjs、jsDelivrなど)や、多数のサイトで共用される静的ファイル配信ドメインの特徴そのものです。
また、自社サイト(例:www.example.jp)の静的ファイルを、異なる登録可能ドメイン(例:static-example.net)で配信していると、Safariではサードパーティとして扱われ、キャッシュが分割されます。
同じ登録可能ドメインのサブドメイン(例:static.example.jp)で配信すれば、ファーストパーティのままです。
CDNを導入する際は、ホスト名をこの観点で設計してください。
各ブラウザの比較
| 項目 | Chrome | Firefox | Safari |
|---|---|---|---|
| キャッシュ上限 | 空き容量から計算(基準80MiB、非Windowsでは最大その16倍) | 空き容量から計算(既定約1GBとの情報あり) | 空き容量から計算(二次資料では最大1GB) |
| 追い出しの基準 | 最終利用からの経過時間×エントリサイズ | frecency(頻度+最終利用時刻) | 未確認 |
| リロード時のサブリソース | 再検証しない | 再検証する(immutableは除く) | 未確認 |
| immutable | 実装あり、既定で無効 | 対応 | 対応(二次資料) |
| 追跡防止機能によるHTTPキャッシュの削除 | なし | なし | ファーストパーティにはなし。サードパーティは分割され、分類されたドメインは検証・削除の対象 |
両OSに共通する、保持期間が短くなる要因
ブラウザの種類にかかわらず、キャッシュの保持期間を短くする要因は5つあります。
- 容量の上限による追い出し
- モバイル端末は空き容量が少ないことが多く、多数のサイトを閲覧するユーザーでは、訪問頻度の低いサイトのリソースほど早く追い出されます。
- キャッシュの分割(パーティショニング)
- 現在の主要ブラウザは、参照元サイトの識別子をキャッシュキーに含める方式(ダブルキーイング)を採用しています。RFC 9111のSection 2でも、ユーザーエージェントのキャッシュがプライバシー上のリスクを避けるために参照元サイトの識別子をキャッシュキーに含めることがあると述べられています(Section 7.2にタイミング攻撃への対策として説明があります)。同じCDN上のファイルでも、別のサイトから読み込まれた場合は再利用されず、別々のエントリとして保存されるため、容量を消費しやすくなります。各ブラウザが導入したバージョンは、確認していません。
- ユーザーの操作
- 履歴やサイトデータの消去、プライベートブラウズ、アプリの再インストールでキャッシュは消えます。
- ブラウザのバージョンアップ
- キャッシュの形式が変わり、キャッシュが破棄される場合があります。
- アプリ内ブラウザ(WebView)は別の小さいキャッシュを持つ
- SNSアプリやメッセージアプリのリンクから開かれたページは、標準ブラウザではなく、そのアプリに埋め込まれたWebViewで表示されます。WebViewのキャッシュはアプリごとに独立しており、標準ブラウザとも他のアプリとも共有されません。詳しくは次の小節で説明します。
アプリ内ブラウザ(WebView)のキャッシュはアプリごとに分かれている
Android WebViewのソースコード(android_webview/browser/aw_browser_context.cc)では、HTTPキャッシュの保存先を次のように決めています。
base::FilePath BuildCachePath(const base::FilePath& relative_path) {
FilePath cache_path;
if (!base::PathService::Get(base::DIR_CACHE, &cache_path)) {
NOTREACHED() << "Failed to get app cache directory for Android WebView";
}
return cache_path.Append(relative_path);
}
base::DIR_CACHE の実体は、Androidの Context.getCacheDir()、つまりWebViewを埋め込んだアプリ自身のキャッシュディレクトリです(base/base_paths_android.cc および base/android/java/src/org/chromium/base/PathUtils.java)。
LINE、X、Instagramのアプリ内ブラウザは、それぞれ別のディレクトリにキャッシュを持ち、Chromeとも共有しません。
容量も、標準ブラウザよりはるかに小さく設定されています(android_webview/browser/network_service/net_helpers.cc)。
int GetHttpCacheSizeInternal() {
constexpr int DEFAULT_CACHE_LIMIT = 20 * 1024 * 1024; // 20MiB
この20MiBは、アプリのキャッシュ割り当てが取得できない場合の値であり、同時に下限でもあります。
現在の実装では、Androidがアプリに割り当てるキャッシュ容量の10%を、20MiBから320MiBの範囲に収めた値が上限になります(android_webview/common/aw_features.cc。この動作は2026年9月25日時点のmainブランチで既定で有効です)。
Chrome本体が非Windowsで基準80MiBの4倍を基準にするのに対し、WebViewは下限20MiB、上限でもChrome本体の基準値と同じ320MiBです。
下限の20MiBは、単純計算で、モバイルのページの重さの中央値(約2.6MB、HTTP Archive Web Almanac 2025)の8ページ分に満たない容量です。
iOSのWKWebViewも同じ構造です。
WebKitのソースコード(Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm)では、既定のネットワークキャッシュを NSCachesDirectory 配下の WebKit/NetworkCache に置いています。
NSCachesDirectory は、iOSではアプリのサンドボックスコンテナ内の Library/Caches なので、WKWebViewのキャッシュもアプリごとに分かれ、Safariとは共有されません。
また、Safariのエンジンをアプリ内に表示するSFSafariViewControllerも、iOS 11以降はSafariとCookieなどのWebサイトデータを共有しなくなっています(この「Webサイトデータ」にHTTPキャッシュが含まれるかどうかは、Appleの一次資料では確認できていません)。
この事実が意味するのは、次のことです。
SNS広告やメッセージ配信からの流入が多いサイトでは、リピーターであっても、初回訪問と同じ状態になりやすいということです。
標準ブラウザで一度訪問したユーザーが、次にSNSアプリのリンクから同じページを開いても、そのアプリのWebViewのキャッシュは空です。
そして、そのWebViewのキャッシュは標準ブラウザより小さいため、次に開くときまで残っている見込みも小さいです。
ただし、すべてのアプリがWebViewを使うわけではありません。
AndroidのChrome Custom Tabsのように、標準ブラウザのエンジンをアプリ内に表示する仕組みでは、標準ブラウザのキャッシュが共有されると理解していますが、この点はソースコードでは確認していません。
どのアプリからの流入が多いか、そのアプリがWebViewを使っているかは、サイトごとに違います。
実務上の結論:リピーターもキャッシュなしの前提で設計する
ここまでの内容から導かれる結論は、次のとおりです。
「リピーターはキャッシュが効いているはず」という前提で性能を設計してはいけません。
特にモバイルでは、再訪時でもキャッシュが残っていない前提で、初回表示の性能を確保しておく必要があります。
弊社が表示開始0.5秒・表示完了1秒以内という目標を、キャッシュのない初回表示で達成することにこだわるのは、このためです。
そして、ブラウザのキャッシュが当てにならないからこそ、CDNのキャッシュが重要になります。
CDNのエッジサーバー(ユーザーの近くに配置され、CDNのキャッシュを保持するサーバー)は、ブラウザとは比較にならない容量を持ち、多数のユーザーで共有されます。
ブラウザのキャッシュが消えていても、エッジサーバーにリソースが残っていれば、ユーザーは最寄りのエッジから即座に取得できます。
Cache-Controlヘッダーで有効期限を正しく指定することの実質的な効果は、ブラウザに長く残すことよりも、CDNのエッジサーバーに長く残すことにあると、私は考えています。
続きの記事
この記事の結論を踏まえて、次の3つの問いを別の記事で扱います。
- max-ageは何秒にすべきか ― ストレージの制約と追い出しの仕組み:リソース種別ごとの推奨値、max-ageと追い出しの関係、ChromiumとFirefoxの追い出し処理のソースコード
- キャッシュの寿命はなぜ確率で扱うのか ― 生存関数、LRU、Cheの近似:キャッシュの領域が有限であることから、生存関数、追い出し方式との関係、Cheの近似まで、寿命を確率で扱う理論
- ブラウザキャッシュのヒット率を生存時間解析で実測する:Resource Timing APIによる判定、生存関数の推定、RUM(Real User Monitoring、実際のユーザーのブラウザで計測する方法)での実装手順
まとめ
- max-ageは「いつまで再確認なしで使ってよいか」を決めるだけで、「いつまで保存するか」は決めません。RFC 9111は、キャッシュに保存の義務を課していません
- Facebookの実測では、44.6%のユーザーが空のキャッシュでアクセスし、モバイルでは半数のリクエストが12時間以内のキャッシュしか持っていませんでした。2007年のYahoo!の調査から、空のキャッシュの割合は下がっていません
- Chrome、Firefox、Safariのいずれも、キャッシュ容量は空き容量から計算され、すべてのサイトで共有され、容量を超えると追い出されます。追い出しの基準に有効期限は含まれません
- SafariのITPの7日間の上限は、HTTPキャッシュには適用されません。ただし、サードパーティのHTTPキャッシュは分割されるため、静的ファイルはサイトと同じ登録可能ドメインで配信します
- アプリ内ブラウザ(WebView)のキャッシュはアプリごとに分かれており、Android WebViewの容量はアプリの割り当ての10%(下限20MiB、上限320MiB)です。WebViewを使うSNSアプリからの流入は、初回訪問と同じ状態になりやすいです
- リピーターもキャッシュなしの前提で初回表示の性能を確保し、キャッシュの実効性はCDNのエッジサーバーで担保します
この記事が事業に意味すること
SNS広告やLINE配信から集客しているサイトでは、リピーターであっても初回訪問と同じ状態になりやすいです。
初回表示が遅ければ、広告費で連れてきたユーザーを、表示を待たせている間に失っています。
ブラウザのキャッシュには期待できないので、対策は初回表示そのものを速くすることしかありません。
弊社は、表示開始0.5秒・表示完了1秒以内を、キャッシュのない初回表示で、98パーセンタイルで達成することを契約で保証しています。
作業の実施ではなく、表示速度という結果に対して責任を負う契約です。
広告費に見合う初回表示を確保したい場合は、お問い合わせフォームからご相談ください。
どのような改善で、どれだけ速くなったかは、高速化成功事例をご覧ください。
参考資料
- RFC 9111: HTTP Caching
- RFC 8246: HTTP Immutable Responses
- Web performance: Cache efficiency exercise(Facebook Engineering、2015年)
- Chromium: net/disk_cache/cache_util.cc
- Reload, reloaded: faster and leaner page reloads(Chromium Blog、2017年)
- Firefox Source Docs: HTTP Cache
- Tracking Prevention in WebKit
- Chromium: android_webview/browser/aw_browser_context.cc
- Chromium: android_webview/browser/network_service/net_helpers.cc
- WebKit: Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm
- Page Weight(HTTP Archive Web Almanac 2025)
- Call to improve browser caching(Steve Souders、2010年)
- max-ageは何秒にすべきか(PerfData)
- キャッシュの寿命はなぜ確率で扱うのか(PerfData)
- ブラウザキャッシュのヒット率を生存時間解析で実測する(PerfData)