Webアプリを作っていると、「ページをリロードしても設定を保持したい」「ユーザーの入力を一時的に記憶したい」という場面に必ず直面します。そのような要件にシンプルかつ強力に応えるのが、ブラウザに標準搭載されているlocalStorageです。
この記事ではlocalStorageの基本操作から始まり、JSONオブジェクトの扱い方、容量エラーへの対処、テーマ切り替えやフォーム自動保存といった実践的なユースケースまでを体系的に解説します。
localStorageとは
localStorageはブラウザが提供するWeb Storage APIのひとつです。キーと値のペアをブラウザ内に永続的に保存でき、ページを閉じても・ブラウザを再起動してもデータは消えません。
オリジン(プロトコル+ドメイン+ポート)ごとに独立した領域が割り当てられるため、異なるサイト間でデータが混ざることはありません。
sessionStorage・Cookieとの違い
似た仕組みとしてsessionStorageとCookieがあります。それぞれの特徴を比較してみましょう。
| localStorage | sessionStorage | Cookie | |
|---|---|---|---|
| 保存期間 | 永続(明示的に削除まで) | タブを閉じるまで | 有効期限を設定(未設定はセッション) |
| 容量 | 約5MB | 約5MB | 約4KB |
| サーバー送信 | なし | なし | 毎リクエストに自動送信 |
| タブ間共有 | 同一オリジンで共有 | タブごとに独立 | 同一オリジンで共有 |
| 用途 | ユーザー設定・カート等 | 一時的なフォーム入力 | 認証トークン・セッション管理 |
sessionStorageとの詳しい使い分けはlocalStorage・sessionStorageの違いと使い分けも参考にしてください。
基本操作
localStorageはグローバルオブジェクトとして利用でき、以下の4つのメソッドが中心になります。
データを保存する(setItem)
第1引数にキー名、第2引数に値(文字列)を渡します。
localStorage.setItem('theme', 'dark');
localStorage.setItem('lang', 'ja');
// 数値は文字列に変換されて保存される
localStorage.setItem('count', 42); // '42' として保存
データを取得する(getItem)
キーが存在しない場合は null が返ります。
const theme = localStorage.getItem('theme');
console.log(theme); // 'dark'
const missing = localStorage.getItem('unknown');
console.log(missing); // null
// null チェックを忘れずに
const lang = localStorage.getItem('lang') ?? 'ja'; // デフォルト値を設定
データを削除する(removeItem)
localStorage.removeItem('theme');
console.log(localStorage.getItem('theme')); // null
すべてのデータを削除する(clear)
localStorage.clear(); console.log(localStorage.length); // 0
保存されているキーを調べる(length・key)
console.log(localStorage.length); // 保存件数
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const val = localStorage.getItem(key);
console.log(key, val);
}
// Object.entries と組み合わせる方法
const all = Object.fromEntries(
Array.from({ length: localStorage.length }, (_, i) => [
localStorage.key(i),
localStorage.getItem(localStorage.key(i)),
])
);
console.log(all);
JSONオブジェクトの保存と取得
localStorageは文字列しか保存できません。オブジェクトや配列を保存するにはJSON.stringify()で文字列に変換し、取り出すときにJSON.parse()で復元します。
// 保存
const user = { name: '山田太郎', age: 30, role: 'admin' };
localStorage.setItem('user', JSON.stringify(user));
// 取得
const raw = localStorage.getItem('user');
const parsed = raw ? JSON.parse(raw) : null;
console.log(parsed?.name); // '山田太郎'
// 保存
const cart = [
{ id: 1, name: 'Tシャツ', qty: 2 },
{ id: 2, name: 'ジーンズ', qty: 1 },
];
localStorage.setItem('cart', JSON.stringify(cart));
// 取得
const savedCart = JSON.parse(localStorage.getItem('cart') ?? '[]');
console.log(savedCart.length); // 2
?? [](二重引用符)など)を用意しましょう。初回アクセスや手動削除後は null が返るためエラーになります。エラーハンドリング
QuotaExceededError(容量超過)
localStorageの容量はブラウザによって異なりますが、おおむね5MB前後です。これを超えるとQuotaExceededErrorが発生します。
function safeSetItem(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.error('localStorageの容量が不足しています');
// 古いキャッシュを削除するなど対処
}
return false;
}
}
プライベートブラウジングへの対応
一部のブラウザはプライベートモードでlocalStorageへの書き込みを制限することがあります。例外が発生した場合のフォールバックを用意しておくと安心です。
function isLocalStorageAvailable() {
try {
const test = '__test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch {
return false;
}
}
if (isLocalStorageAvailable()) {
localStorage.setItem('key', 'value');
} else {
// メモリやCookieなどの代替手段を使う
console.warn('localStorageが使用できません');
}
ユーティリティ関数パターン
毎回try-catchを書くのは煩雑なので、ラッパー関数を用意しておくと保守しやすくなります。
const storage = {
get(key, fallback = null) {
try {
const raw = localStorage.getItem(key);
return raw !== null ? JSON.parse(raw) : fallback;
} catch {
return fallback;
}
},
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
console.error('保存に失敗しました:', e);
return false;
}
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
},
};
// 使い方
storage.set('user', { name: '山田', age: 30 });
const user = storage.get('user', {});
console.log(user.name); // '山田'
実践ユースケース
ダーク/ライトテーマの切り替え
ページ読み込み時に保存済みテーマを適用し、ボタン操作で切り替えて保存する定番パターンです。
// ページ読み込み時に適用
const savedTheme = localStorage.getItem('theme') ?? 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
// 切り替えボタンのイベント
document.getElementById('theme-toggle').addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
フォームデータの自動保存(下書き機能)
入力中のデータを定期的に保存し、誤ってページを閉じても復元できるようにします。
const DRAFT_KEY = 'form_draft';
const form = document.getElementById('contact-form');
// 入力のたびに保存(debounce 推奨)
form.addEventListener('input', () => {
const draft = {
name: form.querySelector('[name="name"]').value,
email: form.querySelector('[name="email"]').value,
message: form.querySelector('[name="message"]').value,
};
localStorage.setItem(DRAFT_KEY, JSON.stringify(draft));
});
// ページ読み込み時に下書きを復元
window.addEventListener('DOMContentLoaded', () => {
const draft = JSON.parse(localStorage.getItem(DRAFT_KEY) ?? 'null');
if (!draft) return;
form.querySelector('[name="name"]').value = draft.name ?? '';
form.querySelector('[name="email"]').value = draft.email ?? '';
form.querySelector('[name="message"]').value = draft.message ?? '';
});
// 送信後に下書きを削除
form.addEventListener('submit', () => {
localStorage.removeItem(DRAFT_KEY);
});
ショッピングカートの簡易実装
const CART_KEY = 'cart';
function getCart() {
return JSON.parse(localStorage.getItem(CART_KEY) ?? '[]');
}
function addToCart(item) {
const cart = getCart();
const existing = cart.find(i => i.id === item.id);
if (existing) {
existing.qty += 1;
} else {
cart.push({ ...item, qty: 1 });
}
localStorage.setItem(CART_KEY, JSON.stringify(cart));
}
function removeFromCart(id) {
const cart = getCart().filter(i => i.id !== id);
localStorage.setItem(CART_KEY, JSON.stringify(cart));
}
// 使い方
addToCart({ id: 1, name: 'Tシャツ', price: 3000 });
addToCart({ id: 1, name: 'Tシャツ', price: 3000 }); // qty: 2 になる
console.log(getCart());
storage イベントでタブ間通信
storage イベントは、同一オリジンの別タブでlocalStorageが変更されたときに発火します。複数タブでの状態同期に活用できます。
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
// 別タブでテーマが変更されたら即座に適用
document.documentElement.setAttribute('data-theme', event.newValue);
}
// event.key : 変更されたキー
// event.oldValue: 変更前の値
// event.newValue: 変更後の値(削除時は null)
// event.url : 変更元ページのURL
console.log(`${event.key}: ${event.oldValue} → ${event.newValue}`);
});
容量制限とブラウザ別の上限
localStorageの容量上限はブラウザによって異なります。一般的には5〜10MB程度ですが、実際の上限は以下のとおりです。
| ブラウザ | 概算上限 | 備考 |
|---|---|---|
| Chrome | 5MB | オリジンごと |
| Firefox | 5〜10MB | 設定変更可 |
| Safari | 5MB | iOS Safariも同様 |
| Edge | 5MB | Chromiumベース |
上限を超えると QuotaExceededError または NS_ERROR_DOM_QUOTA_REACHED(Firefox)が発生します。大量データを保存する場合はIndexedDBの利用を検討してください。
function getStorageUsage() {
let total = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
total += key.length + (localStorage.getItem(key) || '').length;
}
// 2バイト/文字として概算(UTF-16)
return Math.round(total * 2 / 1024) + 'KB';
}
console.log('使用量(目安):', getStorageUsage());
有効期限付きキャッシュの実装
localStorageには有効期限の機能がありません。タイムスタンプと一緒に保存することで、有効期限付きキャッシュを実現できます。APIレスポンスのキャッシュや一時フラグの管理に便利です。
const expiryStorage = {
set(key, value, ttlMs) {
const item = {
value,
expiresAt: ttlMs ? Date.now() + ttlMs : null,
};
try {
localStorage.setItem(key, JSON.stringify(item));
} catch (e) {
console.error('保存に失敗:', e);
}
},
get(key) {
try {
const raw = localStorage.getItem(key);
if (!raw) return null;
const item = JSON.parse(raw);
if (item.expiresAt && Date.now() > item.expiresAt) {
localStorage.removeItem(key); // 期限切れを自動削除
return null;
}
return item.value;
} catch {
return null;
}
},
};
// 使い方: 1時間キャッシュ
expiryStorage.set('api_result', { data: [...] }, 60 * 60 * 1000);
const cached = expiryStorage.get('api_result');
if (cached) {
console.log('キャッシュから取得:', cached);
} else {
console.log('キャッシュ切れ。APIを再取得します');
}
セキュリティ上の注意点
localStorageはJavaScriptから自由に読み書きできます。サイトにXSS脆弱性があると、攻撃者が任意のスクリプトを実行してlocalStorageのデータを盗み取ることが可能です。
- 認証トークン(JWT等)をlocalStorageに保存するのは避ける
- ユーザーに関わる機密情報を平文で保存しない
- localStorageから取得した値をそのままinnerHTMLに代入しない
認証トークンの保存先についてはJavaScriptのCookie操作も参考に、用途に応じて適切な保存場所を選択してください。
DevToolsでのデバッグ方法
ChromeのDevToolsでlocalStorageの中身を確認・編集できます。
- F12(またはCmd+Option+I)でDevToolsを開く
- 「Application」タブを選択
- 左サイドバーの「Storage」→「Local Storage」→対象のオリジンをクリック
- 右ペインでキーと値の確認・追加・編集・削除ができる
// キー一覧をオブジェクト形式で表示
console.table(
Object.fromEntries(
Array.from({ length: localStorage.length }, (_, i) => [
localStorage.key(i),
localStorage.getItem(localStorage.key(i)),
])
)
);
よくある質問
removeItem() または clear() を呼ぶまで永続的に残ります。sessionStorageとは異なり、タブを閉じたりブラウザを再起動しても消えません。setItem() をtry-catchで囲み、QuotaExceededErrorをキャッチして処理します。古いキャッシュデータを定期的にクリアするか、保存前にサイズをチェックする方法も有効です。[object Object] や value1,value2 という文字列として保存されてしまいます。必ず JSON.stringify() で変換してから保存し、取得時は JSON.parse() で復元してください。まとめ
localStorageは数行のコードで使い始められる手軽さが魅力ですが、正しく使いこなすためにはいくつかのポイントを押さえておく必要があります。
- 保存できるのは文字列のみ。オブジェクト・配列はJSON変換が必須
- 容量超過(QuotaExceededError)とプライベートモードへのエラーハンドリングを忘れずに
- 認証トークンなど機密情報は保存しない(XSS対策)
- タブ間で状態を同期したい場合はstorage イベントを活用する
- ラッパー関数を用意してDRYに扱う
テーマ設定やフォーム下書き保存などの実装はlocalStorageが最もシンプルな選択肢です。大容量データや複雑なクエリが必要な場合はIndexedDBの採用も検討してください。
Cookie との使い分けについてはJavaScriptのCookie操作完全ガイドも合わせてご覧ください。