Web ストレージ選定フロー:localStorage / sessionStorage / IndexedDB / Cookie / Cache API の使い分け

約10分

「ユーザーの操作履歴を端末に保存したい」「ログイン状態を保持したい」「画像をオフラインで開けるようにしたい」——いずれもブラウザのストレージ機能を使う場面ですが、選択肢は 5 つあり、それぞれ向き不向きが大きく違います。本記事では特性を比較表に整理し、典型的な要件に対する選定フローを提示します。

5 つのストレージの比較

ストレージ容量目安寿命APIサーバへ送信SSR で読める
localStorage5〜10 MB永続(手動削除まで)同期しないしない
sessionStorage5〜10 MBタブを閉じるまで同期しないしない
IndexedDB数百 MB〜GB永続非同期しないしない
Cookie4 KB / 1 個Expires で指定同期常に送信読める
Cache API数百 MB〜GB永続(明示削除まで)非同期しないしない

サーバに送るか」「容量はどれくらい必要か」「どの寿命にしたいか」「サーバ側でも読みたいか」が主な分岐点です。

localStorage:軽量な永続データ向け

最もよく使われる。設定値・ダーク/ライトの切替・「最後に開いたタブ」のような画面状態に向く。

  • 容量は 5〜10 MB(ブラウザ実装次第、Chrome は 10 MB)
  • 同期 API なので大量書き込みでメインスレッドが詰まる
  • 数値・配列・オブジェクトはすべて文字列にシリアライズされる(JSON.stringify
  • ドメイン単位で分離(origin スコープ)

localStorage の罠

  • JSON シリアライズ前提なので、DateMap がそのままでは保存できないnew Date(JSON.parse(...)) のような復元ロジックが必要
  • 同期 API のため、5MB ギリギリのデータを書くとフリーズが発生(特にモバイル)
  • Safari の Private Browsing ではエラーを投げる(書き込み試行で QuotaExceededError)。try-catch を必ず

sessionStorage:タブ単位の一時状態

API は localStorage と同じだが、タブを閉じると消えるところだけ違う。

  • フォームの未保存入力を一時保存(タブ復帰時に戻す)
  • ウィザード型 UI のステップ間 state
  • ページ遷移を跨いだ「直前の検索クエリ」

タブを開き直されると消えるので、永続化不要なデータの自動退避場所として使うのが正解。

IndexedDB:大量・構造化データ向け

数百 MB〜GB の大容量データ、画像・PDF・JSON 配列の大量保存に向く。

  • 非同期 API(callback / Promise)
  • インデックス・トランザクション・カーソル走査ありの本格的な KV ストア
  • 標準 API は癖があり、Dexieidb のような薄いラッパーを使うのが現実解
  • Workbox 経由で Service Worker と組み合わせる pattern が多い

IndexedDB が向く例

  • オフラインで読めるドキュメント / メール / メッセージのキャッシュ
  • ユーザーがアップロードした画像のローカル保管
  • 大量の検索インデックス(タイムライン全件など)

IndexedDB の罠

  • API が低レベルで複雑。素で書くと onupgradeneeded transaction のミスでデータが入らない事故が起きる
  • ブラウザによっては明示的にユーザーが許可しないと長期間保存されない(Safari の eviction policy)
  • 開発中、devtools の Storage タブから手動でクリアできる(テスト用に便利)

Cookie:サーバとの状態共有

唯一、HTTP リクエストに自動で付与されるストレージ。

  • セッションID、CSRF トークン、認証関連のフラグ
  • 1 個あたり 4 KB、合計 50〜180 個(ブラウザ依存)
  • HttpOnly を付ければ JS から読めない(XSS 耐性)
  • Secure を付ければ HTTPS のみ
  • SameSite で CSRF 耐性

Cookie が必要なケース

  • セッション認証(サーバ側でセッション ID を識別)
  • CSRF トークンの埋め込み
  • A/B テストの実験割当(サーバ側でも判定したい)

Cookie の使いどころでない例

  • 大きなデータ(4KB 制限):localStorage か IndexedDB へ
  • JS でしか読まないデータ:Cookie はリクエストごとにオーバーヘッドになる
  • 別ドメイン間の共有:3rd-party Cookie のサポートが各ブラウザで縮小中(Chrome は 2024 年に廃止計画を撤回したが、Safari / Firefox では既にデフォルト無効)

Cache API:HTTP レスポンスのキャッシュ

Service Worker と組み合わせて、ネットワークレスポンスをそのまま保存

  • cache.put(request, response) でリクエスト/レスポンスのペアを保存
  • オフライン PWA での画像・JS・CSS のキャッシュに最適
  • Request / Response オブジェクトをそのまま扱える(JSON にしなくていい)
  • Workbox がこのレイヤを抽象化してくれる

Cache API が向く例

  • アプリ shell(HTML / CSS / JS)のオフライン化
  • 一覧で表示するサムネイル画像のキャッシュ
  • API レスポンスの ETag / 304 を意識した stale-while-revalidate

JSON データそのものを保存したいなら IndexedDB のほうが向きます(インデックスや更新が楽)。Cache API は HTTP の単位で保存・取り出しすると考えるのが正解。

選定フロー

新しい用途で「どこに保存するか」を決めるとき、以下の順で絞れます。

  1. サーバに毎リクエスト送りたい?

    • YES → Cookie(サイズ制限と CSRF 対策に注意)
    • NO → 次へ
  2. HTTP レスポンスをそのまま保存したい?(PWA / オフライン対応)

    • YES → Cache API + Service Worker
    • NO → 次へ
  3. データサイズが MB 級以上 or 構造化されている?

    • YES → IndexedDB(Dexie 推奨)
    • NO → 次へ
  4. タブを閉じても残したい?

    • YES → localStorage
    • NO → sessionStorage

ユースケース別早見表

  • テーマ(ダーク/ライト)切替 → localStorage
  • 入力中フォームの一時退避 → sessionStorage
  • オフライン対応のドキュメント → IndexedDB(+ Service Worker)
  • PWA の app shell → Cache API(+ Service Worker)
  • ログインセッション → Cookie(HttpOnly + Secure + SameSite)
  • CSRF トークン → Cookie + meta タグの組み合わせ
  • 検索履歴 → localStorage(軽い) or IndexedDB(多量)
  • 画像のオフラインビューア → IndexedDB(バイナリ) or Cache API(HTTP 単位)

容量とライフサイクルの罠

ブラウザは予告なくストレージを削除することがあります

  • Safari:7 日間サイトに訪問がないと localStorage / IndexedDB / Cache API を削除(Storage Access API 関連の制限
  • Chrome / Firefox:ディスク容量逼迫時に LRU で削除
  • eviction の対象になりにくくするには navigator.storage.persist() でユーザーに許可を求める

「localStorage に保存したから永続」は楽観的すぎる。消える可能性を前提に、サーバとの同期 / 復旧経路を用意するのが安全です。

まとめ

5 つのストレージは「容量 × 寿命 × アクセスパターン」の異なるトレードオフを持ちます。Cookie は唯一サーバと連動する特殊な存在、IndexedDB と Cache API は大容量で非同期、localStorage と sessionStorage は気軽だが容量と性能に上限があります。サーバに送るか送らないかを最初に決めると選択肢が一気に絞れます。

実データのサイズ感を確認したいときは、バイト変換ツール で KB / MB の単位を実数で確認できます。