localStorage・sessionStorage・Cookie・IndexedDBは、いずれもブラウザにデータを保存する仕組みですが、容量・データの寿命・サーバーへ自動送信されるか・APIの使い勝手が異なり、用途で使い分けます。おおまかには、タブを閉じたら消えていい一時データは sessionStorage、軽い設定の永続保存は localStorage、サーバーと共有する認証情報は Cookie、大量・構造化データやオフライン用途は IndexedDB が基本の選択肢です。
フロントエンドを書いていると「この値、どこに保存するのが正解だろう?」と迷う場面は必ず来ます。とりあえず何でも localStorage に入れてしまう、認証トークンの置き場所で詰まる、設定値ごときに IndexedDB を持ち出してしまう——保存先の選択を誤ると、セキュリティリスクや無駄な複雑さを抱え込むことになります。
この記事では、4つのストレージの違いを容量・寿命・サーバー送信・同期/非同期の観点で整理し、ユースケース別にどれを選ぶべきかの判断基準を示します。あわせて、やりがちな間違いと、認証トークンの保存先・SSR/hydration での注意点(関連記事)まで踏み込みます。
localStorage・sessionStorage・Cookie・IndexedDBとは
4つはどれも「ブラウザ側にデータを置く」点は同じですが、設計思想が違います。まずは全体像を1枚の表で押さえましょう。容量や寿命、サーバーへ送られるかどうかが、そのまま使い分けの判断軸になります。
| 項目 | localStorage | sessionStorage | Cookie | IndexedDB |
|---|---|---|---|---|
| 容量の目安 | 約5MB前後 | 約5MB前後 | 約4KB/個 | 数百MB〜(大容量) |
| データの寿命 | 削除するまで永続 | タブを閉じると消える | 有効期限を指定 | 削除するまで永続 |
| サーバーへ自動送信 | されない | されない | 毎回される | されない |
| API | 同期 | 同期 | 同期 | 非同期 |
| 保存できる型 | 文字列のみ | 文字列のみ | 文字列のみ | オブジェクト/Blob等 |
| 主な用途 | 設定の永続保存 | 一時的なUI状態 | 認証・サーバー連携 | 大量・構造化データ |
容量は仕様で厳密に決まっているわけではなくブラウザ依存ですが、localStorage・sessionStorage はオリジンごとに概ね5MB前後が目安です(出典: MDN Web Storage API)。Cookie は仕様上1個あたり最低4096バイトの確保が求められており、4KB程度が上限です(出典: MDN HTTP Cookie)。IndexedDB は空きディスクに応じて大容量を扱えます。
localStorageの特徴と向いている用途
localStorage は、同一オリジン内で明示的に削除するまでデータが残り続けるキー・バリュー型のストレージです。APIは同期的で扱いやすく、保存できるのは文字列だけなので、オブジェクトは JSON.stringify で文字列化して保存します。
// 保存(値は文字列化する)
localStorage.setItem("theme", "dark");
localStorage.setItem("user", JSON.stringify({ id: 1, name: "Taro" }));
// 取得
const theme = localStorage.getItem("theme"); // "dark"
const user = JSON.parse(localStorage.getItem("user")); // { id: 1, name: "Taro" }
// 削除
localStorage.removeItem("theme");向いている用途は、ダークモードや言語などのテーマ設定、同意バナーの既読フラグ、軽量なキャッシュなど「タブをまたいで永続的に保持したい軽いデータ」です。実装の具体例は、Webパーソナライズの実装方法 — JavaScript+localStorageでツール不要や、TypeScriptで作る!チェックリストアプリ|ローカル保存&編集機能で実際の使い方を確認できます。
一方で、認証トークンや機密情報の保存には向きません。JavaScript から自由に読み書きできるため、XSS(クロスサイトスクリプティング)が起きると中身を丸ごと盗まれます。この点は後半のセキュリティ章で詳しく扱います。
sessionStorageの特徴と向いている用途
sessionStorage は API も使い方も localStorage とほぼ同じですが、決定的に違うのがデータの寿命です。localStorage が永続するのに対し、sessionStorage はそのタブ(ブラウジングコンテキスト)を閉じると消えます。同じURLでも別タブで開けばデータは共有されず、それぞれ独立しています。
// localStorage と同じAPIで使える
sessionStorage.setItem("step", "2");
sessionStorage.getItem("step"); // "2"
// リロードでは保持されるが、タブを閉じると消える
// 別タブで開いた同じサイトとは共有されない向いている用途は、入力フォームの一時保持、複数ステップのウィザードの途中状態、そのセッション限定で覚えておきたいフラグなど、「持ち越したくない一時データ」です。永続化すると逆に困る情報——たとえば「このタブで一度だけ表示したモーダル」のような状態——は、localStorage ではなく sessionStorage が正解になります。
Cookieの特徴と向いている用途
Cookie が他の3つと根本的に違うのは、HTTPリクエストのたびにサーバーへ自動送信される点です。この性質があるからこそ、サーバー側でユーザーを識別する認証・セッション管理の定番として使われてきました。容量は4KB程度と小さく、保存先としての使い勝手はよくありません。
セキュリティ上、Cookie には必ず意識すべき属性があります。サーバーが Set-Cookie ヘッダで付与する代表的な属性は次のとおりです。
| 属性 | 役割 |
|---|---|
HttpOnly | JavaScriptから読めなくする。XSSでの盗難を防ぐ |
Secure | HTTPS接続でのみ送信する |
SameSite | 他サイトからのリクエスト送信を制限し、CSRFを緩和する |
Expires / Max-Age | 有効期限を指定する(無指定はセッションCookie) |
# サーバーが返す Set-Cookie ヘッダの例(認証トークン)
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600向いている用途は、サーバーと共有する必要があるデータ——とりわけ認証トークンやセッションIDです。HttpOnly を付ければ JavaScript から読めなくなるため、localStorage よりXSSに強くなります。サーバーセッションを使った認証の具体例は、PHPログイン機能の作り方|セッションとpassword_hashで安全に実装する手順が参考になります。
IndexedDBの特徴と向いている用途
IndexedDB は、ブラウザに組み込まれた非同期のNoSQLデータベースです。文字列しか持てない他の3つと違い、オブジェクトや Blob などの構造化データをそのまま保存でき、インデックスを張った検索やトランザクションも使えます。容量も空きディスクに応じて大きく取れます。
// IndexedDB はすべて非同期(イベント駆動)
const req = indexedDB.open("myDB", 1);
req.onupgradeneeded = (e) => {
e.target.result.createObjectStore("items", { keyPath: "id" });
};
req.onsuccess = (e) => {
const db = e.target.result;
// 以降、トランザクションを開いて読み書きする
};向いている用途は、オフライン対応(PWA)、大量データのキャッシュ、画像やファイルの保存など「localStorage では入りきらない/構造化したいデータ」です。ただし生のAPIは記述が冗長なため、実務では Dexie.js のようなラッパーライブラリを併用することが多いです。逆に、設定値が数個あるだけのケースで IndexedDB を持ち出すのは明確なオーバースペックです。
オフライン対応では、IndexedDB と Cache API がよく混同されます。役割が違うので整理しておくと、IndexedDB はアプリのデータ(JSONや構造化データ)を保存するもの、Cache API は Service Worker と組み合わせてHTTPリクエストとレスポンス(HTML・CSS・画像などのファイル)をキャッシュするものです。PWAでは「データはIndexedDB、ページやアセットはCache API」と使い分けるのが定石です。
ストレージを扱うときの共通の注意点
どれを選ぶかとは別に、ブラウザストレージ全般に共通する「使う前に知っておくべき挙動」があります。ここを外すと、保存先の選択が正しくても実装でつまずきます。
文字列しか保存できない(IndexedDB以外)
localStorage・sessionStorage・Cookie はいずれも値を文字列としてしか保存できません。オブジェクトや配列は JSON.stringify で文字列化して保存し、取り出すときに JSON.parse で戻します。このとき、壊れた値や旧フォーマットが残っていると JSON.parse が例外を投げるため、try/catch で防御するのが安全です。
同期APIはメインスレッドをブロックする
localStorage・sessionStorage の読み書きは同期処理で、実行中はメインスレッド(UI)が止まります。少量のデータなら問題になりませんが、巨大なデータを頻繁に読み書きすると画面がカクつく原因になります。大量データを扱うなら、非同期で動く IndexedDB を選ぶべき理由のひとつがこれです。
storageイベントでタブ間の同期ができる
localStorage の変更は、同じオリジンの他のタブに storage イベントとして通知されます。これを使うと、たとえば「片方のタブでログアウトしたら、開いている全タブを一斉にログアウトさせる」といった同期が実装できます。なお、タブごとに独立している sessionStorage はこのイベントの対象外です。
// 別タブで localStorage が変わると発火する
window.addEventListener("storage", (e) => {
if (e.key === "auth" && e.newValue === null) {
// 他タブでログアウトされた → このタブもログアウト処理
location.reload();
}
});プライベートモードやブラウザ設定で使えないことがある
プライベートブラウジングやトラッキング防止機能が有効な環境では、Cookie やストレージが制限・無効化されることがあります。保存に失敗してもアプリ全体が落ちないよう、書き込みは try/catch で包み、保存できないケースのフォールバックを用意しておくと堅牢になります。
4つの使い分け早見表
ここまでの特徴を、判断軸ごとに逆引きできる形でまとめます。「何を基準に選ぶか」が決まれば、候補は自然に絞れます。
| 判断軸 | 結論 |
|---|---|
| サーバーに毎回送る必要がある | Cookie 一択(他は送信されない) |
| データ量が大きい・構造化したい | IndexedDB(他は容量が小さい/文字列のみ) |
| タブを閉じたら消えていい一時データ | sessionStorage |
| 軽い設定を永続保存したい | localStorage |
| 機密情報・認証情報を扱う | Cookie(HttpOnly)。localStorage は避ける |
ユースケース別の選び方
実際の場面に当てはめると、判断はさらにシンプルになります。次の順に自問していくと、ほとんどのケースで保存先が決まります。
- サーバーに毎回送る必要があるか?(認証・セッション)→ Cookie(HttpOnly)
- 大量・構造化データ、またはオフライン対応が必要か?→ IndexedDB
- タブを閉じたら消えていい一時データか?→ sessionStorage
- それ以外の、軽くて永続させたいデータか?→ localStorage
代表的なユースケースごとの推奨をまとめると、次のようになります。
| やりたいこと | 推奨 | 理由 |
|---|---|---|
| 認証トークン / セッション | Cookie(HttpOnly) | サーバーへ送信が必要で、JSから隠せる |
| ダークモード等の設定 | localStorage | 永続・軽量・同期APIで十分 |
| フォーム入力の一時保持 | sessionStorage | タブ内で完結し、閉じれば破棄される |
| オフラインデータ / 大量キャッシュ | IndexedDB | 大容量・構造化・非同期 |
| 未ログインのカート | localStorage または IndexedDB | 量が少なければ localStorage で十分 |
やりがちな間違い・アンチパターン
保存先選びで実際によく起きる失敗を挙げます。どれも「動くけれど、後で問題になる」タイプの間違いです。
- 認証トークンを localStorage に保存する:XSSが起きると全部盗まれる。最も多く、最も危険なパターン(詳細は次章)
- 機密情報を平文で保存する:クライアント側で暗号化しても復号鍵もクライアントにある以上、根本的な保護にはならない
- 何でも localStorage に入れる:タブ間で共有したくない一時状態まで永続化してしまい、別タブで意図しない値が残る
- IndexedDB の過剰採用:設定が数個あるだけなのに非同期DBを持ち出し、コードを無駄に複雑にする
JSON.parseの例外を握りつぶす:壊れた値や旧フォーマットが残っていると例外で画面が落ちる。try/catch で防御する- 容量超過を想定しない:localStorage が上限に達すると
QuotaExceededErrorが投げられる。保存処理は失敗しうる前提で書く
セキュリティの注意点:トークンをどこに置くか
保存先の判断で最もシビアなのが、認証トークンの置き場所です。前提として、XSSでページ上のJavaScriptが乗っ取られると、localStorage と sessionStorage の中身は無防備に読み出せます。Cookie も HttpOnly が付いていなければ同様です。
そのため、認証トークンは HttpOnly + Secure + SameSite を付けた Cookie に置くのが基本方針になります。「localStorage にJWTを保存する」という設計が安全といえるのはどんな条件か、トレードオフの詳細は localStorageにJWTを保存すべきか|XSSリスクと安全なトークン管理 で掘り下げています。
フレームワーク利用時の注意:SSRとhydration
Next.js や Remix などSSR(サーバーサイドレンダリング)を行うフレームワークでは、サーバー上に localStorage・sessionStorage・document は存在しません。サーバーで生成したHTMLと、ブラウザで localStorage を読んで描画した結果がズレると、React はhydration error(ハイドレーションの不一致)を起こします。
対策の基本は、ストレージの読み取りを useEffect 内(=クライアントでのみ実行される場所)に寄せ、初期レンダリングはサーバーと同じ結果にすることです。具体的な再現条件と解決パターンは Next.jsでlocalStorageがhydration errorになる原因と解決法 で詳しく解説しています。
よくある質問
Q. localStorageとsessionStorageの一番の違いは何ですか?
データの寿命です。localStorage は明示的に削除するまで永続しますが、sessionStorage はタブを閉じると消えます。また sessionStorage はタブごとに独立しており、別タブとは共有されません。APIの使い方自体は同じです。
Q. 認証トークンはどこに保存すべきですか?
HttpOnly 属性を付けた Cookie が基本です。localStorage や sessionStorage は JavaScript から読めるため、XSSが起きるとトークンを盗まれます。トレードオフの詳細は localStorageにJWTを保存すべきか を参照してください。
Q. Cookieはもう古くて、localStorageで代替できますか?
用途が違うため、単純な代替はできません。サーバーへ自動送信されるのは Cookie だけで、認証・セッション管理はこの性質に依存します。サーバー連携が不要な純粋なクライアント保存であれば localStorage が適しています。役割が異なると考えてください。
Q. IndexedDBはどんなときに使えばいいですか?
大量データのキャッシュ、オフライン対応(PWA)、画像やファイルなど構造化データの保存といった、localStorage では入りきらない/文字列では扱いにくいケースです。逆に設定値が数個あるだけなら、IndexedDB は過剰で localStorage で十分です。
Q. localStorageの容量上限はどれくらいですか?
仕様で厳密に決まってはおらずブラウザ依存ですが、オリジンごとに概ね5MB前後が目安です(出典: MDN)。上限に達すると保存時に QuotaExceededError が発生するため、書き込みは失敗しうる前提で実装します。
Q. 保存したデータは別のサイトから見えてしまいますか?
いいえ。4つのストレージはいずれもオリジン(スキーム+ホスト+ポートの組み合わせ)ごとに分離されており、別オリジンのページから読み出すことはできません。たとえば https://example.com で保存した localStorage は、https://other.com からは一切参照できません。逆に言えば、同一オリジン内であればパスが違っても共有される点には注意が必要です。
まとめ
localStorage・sessionStorage・Cookie・IndexedDB は、容量・寿命・サーバー送信の有無・APIの種類で性格が分かれます。サーバーへ送るなら Cookie、大量・構造化なら IndexedDB、一時データなら sessionStorage、軽い永続データなら localStorage——この4分岐を押さえれば、ほとんどの保存先は迷わず決められます。
特に認証トークンは、安易に localStorage へ置かず HttpOnly Cookie を基本にすること、SSR環境では hydration error に注意することの2点が実務での要注意ポイントです。それぞれの詳細は関連記事で深掘りしているので、あわせて確認してみてください。
