JavaScript | Web API:通信・ネットワーク系 - キャッシュ制御

JavaScript JavaScript
スポンサーリンク

「キャッシュ制御」は“いつまで古いデータを使っていいか”を決めるルール

まずざっくりイメージからいきます。
キャッシュは、「一度取ってきたデータを、次回以降は早く返すために手元に置いておく仕組み」 です。

ブラウザや中継のプロキシサーバーは、
サーバーから受け取ったレスポンスを保存しておいて、
「また同じものが欲しい」と言われたときに、
わざわざサーバーまで取りに行かずに手元から返すことがあります。

これ自体はとても良いことですが、
問題は 「いつまでそれを“新しい”とみなしていいのか」 です。

キャッシュ制御とは、
まさにこの「古いデータをどこまで許すか」を
ヘッダーで決める仕組みです。


キャッシュ制御の主役:Cache-Control ヘッダー

max-age で「何秒間は新鮮か」を決める

一番よく使うのが Cache-Control: max-age=秒数 です。

例えば、サーバーがこう返したとします。

Cache-Control: max-age=60

これは、

「このレスポンスは 60 秒間は新鮮(fresh)とみなしていいよ」

という意味です。

ブラウザはこのレスポンスをキャッシュに保存し、
同じリクエストが 60 秒以内に来たら、
サーバーに行かずにキャッシュから返すことができます。

逆に、60 秒を過ぎると「古い(stale)」とみなされ、
サーバーに取りに行く対象になります。

no-store / no-cache の違い

ここは名前で混乱しやすいところなので、丁寧にいきます。

Cache-Control: no-store
「このレスポンスはどこにも保存するな」
ブラウザもプロキシもキャッシュに残してはいけない。
毎回サーバーに取りに行く。

Cache-Control: no-cache
「キャッシュしてもいいけど、使う前に必ずサーバーに確認しろ」
ブラウザはキャッシュを持つが、再利用前に条件付きリクエストを送る。

つまり、

絶対に保存させたくない → no-store
保存はしていいが、再利用前に必ず確認 → no-cache

という違いです。

パスワードリセットページや、
機密性の高いページは no-store にすることが多いです。


ETag / Last-Modified と「条件付きリクエスト」

「変わってなかったらキャッシュをそのまま使っていい?」と聞きに行く

no-cache のように「確認してから使う」場合、
ブラウザは「条件付きリクエスト」を送ります。

ここで出てくるのが ETagLast-Modified です。

サーバーがレスポンスにこう付けていたとします。

ETag: "abc123"
Cache-Control: no-cache

ブラウザはこれを覚えておき、
次回同じリソースを取りに行くときにこう聞きます。

If-None-Match: "abc123"

サーバー側は、

「中身変わってないよ」なら 304 Not Modified を返す
「変わったよ」なら 200 OK と新しいボディを返す

という挙動をします。

ブラウザは 304 のとき、
「じゃあ前のキャッシュをそのまま使おう」と判断します。

これが「キャッシュを持ちつつ、古くなっていないか確認する」仕組みです。


fetch とキャッシュ制御の関係

デフォルトでは「ブラウザのキャッシュ戦略」に従う

fetch を何も指定せずに使うと、
基本的にはブラウザの通常のキャッシュルールに従います。

const res = await fetch("/api/data");
JavaScript

このとき、

サーバーが Cache-ControlETag をどう返しているか
ブラウザがそのリソースをどうキャッシュしているか

によって、
実際にネットワークに行くか、キャッシュから返るかが決まります。

JavaScript 側からは、
「キャッシュを使うかどうか」をある程度指定することもできます。

fetch の cache オプションで挙動を変える

fetch には cache オプションがあります。

fetch("/api/data", { cache: "no-store" });
JavaScript

代表的な値だけ押さえましょう。

default
ブラウザの通常のキャッシュルールに従う(デフォルト)。

no-store
キャッシュを一切使わないし、保存もしない。
毎回ネットワークに行く。

reload
キャッシュを無視してネットワークから取得し、
結果でキャッシュを更新する。

no-cache
キャッシュを使う前に必ずサーバーに確認する(条件付きリクエスト)。

force-cache
可能な限りキャッシュを使う。
なければネットワークに行く。

only-if-cached
キャッシュにある場合だけ使う。
なければエラー(同一オリジン限定)。

初心者としてまず覚えておくといいのは、

「絶対に最新が欲しい」 → cache: "no-store" or cache: "reload"
「普通でいい」 → 何も指定しない(default

くらいで十分です。


具体例:API のレスポンスをキャッシュさせたくない場合

サーバー側の設定

例えば、ログイン状態や個人情報を返す API で、
ブラウザやプロキシにキャッシュさせたくない場合。

サーバーはこう返します。

Cache-Control: no-store

これで、ブラウザはそのレスポンスをキャッシュに保存しません。
毎回サーバーに取りに行きます。

クライアント側での fetch

クライアント側でも念のためこう書けます。

const res = await fetch("/api/me", {
  cache: "no-store",
});
JavaScript

サーバー側の Cache-Control: no-store が本命ですが、
クライアント側でも「キャッシュを使わない」と明示しておくと安心です。

ここでのポイントは、

「本当にキャッシュさせたくないかどうかは、サーバー側が決める」
ということです。

fetchcache オプションは「ブラウザに対するお願い」に近く、
最終的な権限はレスポンスヘッダー側にあります。


具体例:静的ファイルをガッツリキャッシュさせたい場合

ビルド済みの JS / CSS など

SPA などで、ビルド済みの JS / CSS ファイルに
ハッシュ付きのファイル名を付けている場合(例: app.abc123.js)、
そのファイルは「内容が変わったらファイル名も変わる」前提です。

この場合、サーバーはこう返せます。

Cache-Control: max-age=31536000, immutable

max-age=31536000 は 1 年
immutable は「この URL の中身は変わらない」と宣言するもの

ブラウザは一度取ってきたら、
ほぼ二度とネットワークに行かずキャッシュから返します。

新しいビルドが出たら、
ファイル名が app.def456.js のように変わるので、
そのときだけ新しいファイルを取りに行きます。

これが「キャッシュを前提にしたフロントエンドの定番パターン」です。


初心者としてキャッシュ制御で本当に掴んでほしいこと

キャッシュ制御は「いつまで古いデータを使っていいか」を決めるルール
主役は Cache-Control ヘッダー(max-age / no-store / no-cache など)
no-store は「保存禁止」、no-cache は「使う前に確認」
ETagLast-ModifiedIf-None-Match / If-Modified-Since で「変わったかどうか」を確認できる
fetchcache オプションで、ある程度ブラウザのキャッシュの使い方を指定できる
「絶対に最新が欲しい API」と「ガッツリキャッシュしていい静的ファイル」を意識的に分ける

一番いい練習は、
ブラウザの DevTools の「Network」タブを開いて、

同じリソースを何度かリロードしてみる
ステータスが「200」から「304」になったり、「from disk cache」になったりするのを見る
レスポンスヘッダーの Cache-Control / ETag を眺める

ことです。

キャッシュ制御は、
「なんとなく速くなっている裏側の仕組み」ではなく、
「あなたが設計できるパフォーマンスの武器」 です。
そこに気づけると、ネットワーク周りの設計が一段楽しくなります。

タイトルとURLをコピーしました