plan-4ging

2026/04/04

Web Storage とは

ブラウザにデータをKey-Value 形式で保存できる仕組み

storage.setItem('key', 'value')   // 保存
storage.getItem('key')            // 取得
storage.removeItem('key')         // 削除
storage.clear()                   // 全削除

sessionStorage と localStorage

sessionStoragelocalStorage
データ有効期限タブ・ウィンドウを閉じるまで明示的に削除するまで(永続)
スコープタブ単位オリジン単位(全タブで共有)
複数オリジン間共有不可不可
JS からのアクセス可能可能
sessionStorage:
 タブA(example.com)→ 独自ストレージ
 タブB(example.com)→ 独自ストレージ(タブAと別)
 → 同じサイトでも別タブには共有されない

localStorage:
 タブA(example.com)→ 共有ストレージ
 タブB(example.com)→ 共有ストレージ(タブAと同じ)
 → 同一オリジンなら全タブで共有される
CookiesessionStoragelocalStorage
有効期限設定可能(Expires/Max-Ageタブを閉じるまで永続
JS からのアクセスHttpOnlyで制限可常に可能常に可能
XSS に対する安全性HttpOnlyで保護可能脆弱(常に JS からアクセス可)脆弱(常に JS からアクセス可)
複数オリジン間共有Domain属性で可不可不可

sessionStorage

基本操作

// 保存
sessionStorage.setItem('search_query', '検索ワード')

// オブジェクトは JSON にシリアライズして保存
const filters = { category: 'tech', sort: 'newest', page: 2 }
sessionStorage.setItem('search_filters', JSON.stringify(filters))
const savedFilters = JSON.parse(sessionStorage.getItem('search_filters') ?? '{}')

// 取得
const query = sessionStorage.getItem('search_query')

// 削除
sessionStorage.removeItem('search_query')

// 全削除
sessionStorage.clear()

ユースケース:複数ステップのフォーム(ウィザード)

ステップをまたいでフォームデータを保持する
タブを閉じると消えてよい一時データに最適

class WizardFormStorage {
  static STORAGE_KEY = 'wizard_form_data'

  static save(step, data) {
    const current = this.load()
    const updated = { ...current, [`step${step}`]: data }
    sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(updated))
  }

  static load() {
    return JSON.parse(sessionStorage.getItem(this.STORAGE_KEY) ?? '{}')
  }

  static getStep(step) {
    return this.load()[`step${step}`] ?? {}
  }

  static clear() {
    sessionStorage.removeItem(this.STORAGE_KEY)
  }
}

// ステップ1:基本情報を保存
WizardFormStorage.save(1, { name: 'test', email: 'xxx' })

// ステップ2:支払い情報を保存
WizardFormStorage.save(2, { plan: 'premium' })

// 最終確認ページ:全ステップのデータを取得
const allData = WizardFormStorage.load()

// 送信完了後にクリア
WizardFormStorage.clear()

ユースケース:ページリロード時の状態復元

タブ選択などページを離れると消えてよい UI 状態などを保存

// 選択中のタブを保存
function selectTab(tabId) {
  sessionStorage.setItem('active_tab', tabId)
  renderTab(tabId)
}

function initTabs() {
  const savedTab = sessionStorage.getItem('active_tab') ?? 'tab-1'
  renderTab(savedTab)
}

localStorage

基本操作

// 保存・取得(sessionStorage と同じ API)
localStorage.setItem('theme', 'dark')
const theme = localStorage.getItem('theme')

ユースケース:テーマ・言語設定

ユーザーの好みを永続化する典型的なユースケース

const ThemeStorage = {
  KEY: 'app_theme',

  get() {
    return localStorage.getItem(this.KEY) ?? 'light'
  },

  set(theme) {
    localStorage.setItem(this.KEY, theme)
    document.documentElement.setAttribute('data-theme', theme)
  },

  toggle() {
    const current = this.get()
    this.set(current === 'light' ? 'dark' : 'light')
  },

  init() {
    // ページ読み込み時に即座に適用する
    document.documentElement.setAttribute('data-theme', this.get())
  },
}

// <head> の中で早期に実行する(FOUC = Flash of Unstyled Content 防止)
ThemeStorage.init()

ユースケース:下書き・入力の自動保存

フォームの入力内容を自動保存してブラウザを閉じても復元できるようにする

class AutoSaveDraft {
  constructor(formId, storageKey) {
    this.form       = document.getElementById(formId)
    this.storageKey = storageKey
    this.saveTimer  = null
    this.init()
  }

  init() {
    // 保存済みの下書きを復元
    const draft = localStorage.getItem(this.storageKey)
    if (draft) {
      const data = JSON.parse(draft)
      this.restoreForm(data)
    }

    // 入力のたびに300ms 後に保存(デバウンス)
    this.form.addEventListener('input', () => {
      clearTimeout(this.saveTimer)
      this.saveTimer = setTimeout(() => this.save(), 300)
    })

    // 送信完了時に下書きを削除
    this.form.addEventListener('submit', () => this.clear())
  }

  save() {
    const data = {
      ...Object.fromEntries(new FormData(this.form)),
      savedAt: new Date().toISOString(),
    }
    localStorage.setItem(this.storageKey, JSON.stringify(data))
  }

  restoreForm(data) {
    Object.entries(data).forEach(([key, value]) => {
      const field = this.form.elements[key]
      if (field) field.value = value
    })
  }

  clear() {
    localStorage.removeItem(this.storageKey)
  }
}

// 使用例
const draft = new AutoSaveDraft('post-form', 'post_draft_new')

ユースケース:閲覧履歴・最近見たアイテム

EC サイトの「最近見た商品」のような機能

class RecentlyViewed {
  static KEY      = 'recently_viewed'
  static MAX_ITEMS = 10

  static add(item) {
    const items  = this.get()
    // 同じアイテムがあれば先頭に移動する(重複除去)
    const filtered = items.filter(i => i.id !== item.id)
    const updated  = [{ ...item, viewedAt: Date.now() }, ...filtered]
      .slice(0, this.MAX_ITEMS)  // 上限を超えたら古いものを削除

    localStorage.setItem(this.KEY, JSON.stringify(updated))
  }

  static get() {
    return JSON.parse(localStorage.getItem(this.KEY) ?? '[]')
  }

  static clear() {
    localStorage.removeItem(this.KEY)
  }
}

// 商品ページを訪問したときに記録
RecentlyViewed.add({ id: '123', name: 'スニーカー', image: '/img/sneaker.jpg' })

// 最近見た商品を表示
const items = RecentlyViewed.get()

注意点

storage イベントでタブ間の状態を同期する

localStorage の変更は同一オリジンの他タブに storage イベントで通知される

window.addEventListener('storage', (event) => {
  if (event.key === 'app_theme') {
    // 別タブでテーマが変更されたら現在のタブにも適用する
    const theme = event.newValue ?? 'light'
    document.documentElement.setAttribute('data-theme', theme)
  }

  if (event.key === 'auth_token' && event.newValue === null) {
    // 別タブでログアウトされたら現在のタブもログアウト画面に遷移する
    window.location.href = '/login'
  }
})

プライベートモード・環境による制限

ブラウザや設定により、挙動が不安定になる場合がある
事前にテスト行い、挙動を確認しておくと安全

まとめ

sessionStorage を使う場面

  • 複数ステップのフォームの一時データ
  • 検索条件・フィルタ(タブを閉じたらリセットでよい)
  • ページリロード時だけ復元したい UI 状態

localStorage を使う場面

  • テーマ・言語などユーザー設定の永続化
  • フォームの下書き自動保存
  • 最近見た商品・閲覧履歴
  • 認証トークン(HttpOnly・Secure で保護)
  • サーバーが読む必要のある値
  • ドメイン間で共有が必要な値

参考