【JavaScript】localStorageの使い方完全ガイド|基本操作・JSON保存・エラー処理・実践パターンまで解説

Webアプリを作っていると、「ページをリロードしても設定を保持したい」「ユーザーの入力を一時的に記憶したい」という場面に必ず直面します。そのような要件にシンプルかつ強力に応えるのが、ブラウザに標準搭載されているlocalStorageです。

この記事ではlocalStorageの基本操作から始まり、JSONオブジェクトの扱い方、容量エラーへの対処、テーマ切り替えやフォーム自動保存といった実践的なユースケースまでを体系的に解説します。

スポンサーリンク

localStorageとは

localStorageはブラウザが提供するWeb Storage APIのひとつです。キーと値のペアをブラウザ内に永続的に保存でき、ページを閉じても・ブラウザを再起動してもデータは消えません

オリジン(プロトコル+ドメイン+ポート)ごとに独立した領域が割り当てられるため、異なるサイト間でデータが混ざることはありません。

sessionStorage・Cookieとの違い

似た仕組みとしてsessionStorageCookieがあります。それぞれの特徴を比較してみましょう。

localStorage sessionStorage Cookie
保存期間 永続(明示的に削除まで) タブを閉じるまで 有効期限を設定(未設定はセッション)
容量 約5MB 約5MB 約4KB
サーバー送信 なし なし 毎リクエストに自動送信
タブ間共有 同一オリジンで共有 タブごとに独立 同一オリジンで共有
用途 ユーザー設定・カート等 一時的なフォーム入力 認証トークン・セッション管理

sessionStorageとの詳しい使い分けはlocalStorage・sessionStorageの違いと使い分けも参考にしてください。

基本操作

localStorageはグローバルオブジェクトとして利用でき、以下の4つのメソッドが中心になります。

データを保存する(setItem)

第1引数にキー名、第2引数に値(文字列)を渡します。

setItem の基本
localStorage.setItem('theme', 'dark');
localStorage.setItem('lang', 'ja');

// 数値は文字列に変換されて保存される
localStorage.setItem('count', 42); // '42' として保存

データを取得する(getItem)

キーが存在しない場合は null が返ります。

getItem の基本
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)

removeItem の基本
localStorage.removeItem('theme');
console.log(localStorage.getItem('theme')); // null

すべてのデータを削除する(clear)

clear() はそのオリジンのすべてのデータを削除します。ログアウト処理などで使用しますが、関係ないキーまで消えてしまうため、通常は removeItem() を使うほうが安全です。
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 チェックまたはデフォルト値(?? [](二重引用符)など)を用意しましょう。初回アクセスや手動削除後は 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を書くのは煩雑なので、ラッパー関数を用意しておくと保守しやすくなります。

Storage ユーティリティ
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が変更されたときに発火します。複数タブでの状態同期に活用できます。

storage イベントは変更を行ったタブ自身では発火しません。別タブから変更があった場合のみ検知できます。
storage イベントの監視
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を再取得します');
}

セキュリティ上の注意点

XSS(クロスサイトスクリプティング)に注意
localStorageはJavaScriptから自由に読み書きできます。サイトにXSS脆弱性があると、攻撃者が任意のスクリプトを実行してlocalStorageのデータを盗み取ることが可能です。

  • 認証トークン(JWT等)をlocalStorageに保存するのは避ける
  • ユーザーに関わる機密情報を平文で保存しない
  • localStorageから取得した値をそのままinnerHTMLに代入しない

認証トークンの保存先についてはJavaScriptのCookie操作も参考に、用途に応じて適切な保存場所を選択してください。

DevToolsでのデバッグ方法

ChromeのDevToolsでlocalStorageの中身を確認・編集できます。

  1. F12(またはCmd+Option+I)でDevToolsを開く
  2. 「Application」タブを選択
  3. 左サイドバーの「Storage」→「Local Storage」→対象のオリジンをクリック
  4. 右ペインでキーと値の確認・追加・編集・削除ができる
コンソールからの確認
// キー一覧をオブジェクト形式で表示
console.table(
  Object.fromEntries(
    Array.from({ length: localStorage.length }, (_, i) => [
      localStorage.key(i),
      localStorage.getItem(localStorage.key(i)),
    ])
  )
);

よくある質問

QlocalStorageのデータはいつ消えますか?
Aユーザーがブラウザのデータを手動削除するか、コードで removeItem() または clear() を呼ぶまで永続的に残ります。sessionStorageとは異なり、タブを閉じたりブラウザを再起動しても消えません。
Qシークレット(プライベート)モードでも使えますか?
A多くのブラウザではシークレットモードでもlocalStorageを使用できますが、セッション終了時にデータが消える場合があります。また、書き込みを完全に拒否するブラウザもあるため、try-catchで対処しておくことを推奨します。
Q容量を超えたときのエラーを防ぐには?
AsetItem() をtry-catchで囲み、QuotaExceededErrorをキャッチして処理します。古いキャッシュデータを定期的にクリアするか、保存前にサイズをチェックする方法も有効です。
QlocalStorageとsessionStorageはどう使い分ければいいですか?
Aページを閉じても保持したい設定(テーマ、言語、カートなど)はlocalStorage、タブを閉じたら消えていい一時データ(ウィザードの途中状態など)はsessionStorageが適しています。詳しくはlocalStorage・sessionStorageの違いと使い分けをご覧ください。
Qオブジェクトや配列をそのまま保存するとどうなりますか?
A[object Object]value1,value2 という文字列として保存されてしまいます。必ず JSON.stringify() で変換してから保存し、取得時は JSON.parse() で復元してください。

まとめ

localStorageは数行のコードで使い始められる手軽さが魅力ですが、正しく使いこなすためにはいくつかのポイントを押さえておく必要があります。

  • 保存できるのは文字列のみ。オブジェクト・配列はJSON変換が必須
  • 容量超過(QuotaExceededError)とプライベートモードへのエラーハンドリングを忘れずに
  • 認証トークンなど機密情報は保存しない(XSS対策)
  • タブ間で状態を同期したい場合はstorage イベントを活用する
  • ラッパー関数を用意してDRYに扱う

テーマ設定やフォーム下書き保存などの実装はlocalStorageが最もシンプルな選択肢です。大容量データや複雑なクエリが必要な場合はIndexedDBの採用も検討してください。

Cookie との使い分けについてはJavaScriptのCookie操作完全ガイドも合わせてご覧ください。