フロント実装で「ちょっとデータを置いておきたい」場面、よくありますよね。この記事は Web Storage(localStorage / sessionStorage) と IndexedDB を、設計の観点からまとめた備忘録です。初学者の方でも読めるよう、決め方・落とし穴・最小コードをギュッと詰めました。
概要
業務のwebアプリケーション開発のなかで、クライアントサイドでの一時データ保存をすることになりました。
localStorageは使ったことがありますが、ブラウザストレージについて理解が浅い状態のため、備忘録としてこれを残します。
想定読者
- フロントエンド言語の初学者
- WebWorker,ブラウザストレージ(LocalStorage, IndexedDB)の初学者
- ブラウザストレージのことを改めて確認したい開発者
ざっくり違い
| 項目 | localStorage | sessionStorage | IndexedDB |
|---|---|---|---|
| データ型 | 文字列のみ(JSONで自前変換) | 文字列のみ | 構造化オブジェクト(Date, Blob, ArrayBuffer など) |
| スコープ | 同一オリジンで共有(全タブ) | 同一タブ/ウィンドウのみ | 同一オリジン。DB→オブジェクトストア単位 |
| ライフサイクル | 明示削除まで(容量圧迫時はブラウザが蒸発し得る) | タブ/ウィンドウを閉じるまで | 半恒久(容量とストレージポリシー依存) |
| API 特性 | 同期(メインスレッドをブロック) | 同期 | 非同期(Promise/イベント、トランザクション) |
| 容量目安 | 数 MB 程度 | 数 MB 程度 | 数十〜数百 MB(実装/空き容量/許可に依存) |
| 検索性 | キーのみ | キーのみ | インデックス/レンジ/カーソル検索 |
| Worker から | 直接不可 | 直接不可 | 可(Web Worker / Service Worker から) |
| 代表イベント |
storage(他タブ通知) |
なし |
upgradeneeded など |
補足: プライベートブラウジングでは容量や永続性が制限される場合がある
ざっくり使い分け
[保存したいデータは小さくて文字列だけ?]
├─ はい → [タブを跨いで共有したい?]
│ ├─ はい → localStorage
│ └─ いいえ → sessionStorage
└─ いいえ → [大容量/複雑検索/バイナリ/オフライン要件?]
├─ はい → IndexedDB
└─ いいえ → 小規模でも将来拡張を見込むなら IndexedDB、
そうでなければ localStorage で十分
最小コード例
localStorage
// 保存
localStorage.setItem('prefs', JSON.stringify({ theme: 'dark' }));
// 取得
const prefs = JSON.parse(localStorage.getItem('prefs') ?? '{}');
sessionStorage
sessionStorage.setItem('flowStep', 'shipping');
const step = sessionStorage.getItem('flowStep');
IndexedDB(素のAPI最短)
const openReq = indexedDB.open('app-db', 1);
openReq.onupgradeneeded = () => {
const db = openReq.result;
const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
store.createIndex('byUpdatedAt', 'updatedAt');
};
openReq.onsuccess = () => {
const db = openReq.result;
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
store.put({ title: 'hello', updatedAt: Date.now() });
tx.oncomplete = () => db.close();
};
IndexedDB(idb ラッパー)
import { openDB } from 'idb';
const db = await openDB('app-db', 1, {
upgrade(db) {
const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
store.createIndex('byUpdatedAt', 'updatedAt');
},
});
await db.put('notes', { title: 'hello', updatedAt: Date.now() });
const latest = await db.getAllFromIndex('notes', 'byUpdatedAt');
誤りや補足があればコメントでぜひ教えてください。改善していきます!