2026/04/04
Web Storage とは
ブラウザにデータをKey-Value 形式で保存できる仕組み
storage.setItem('key', 'value') // 保存
storage.getItem('key') // 取得
storage.removeItem('key') // 削除
storage.clear() // 全削除
sessionStorage と localStorage
| sessionStorage | localStorage | |
|---|---|---|
| データ有効期限 | タブ・ウィンドウを閉じるまで | 明示的に削除するまで(永続) |
| スコープ | タブ単位 | オリジン単位(全タブで共有) |
| 複数オリジン間共有 | 不可 | 不可 |
| JS からのアクセス | 可能 | 可能 |
- https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API
- https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage
- https://developer.mozilla.org/ja/docs/Web/API/Window/sessionStorage
sessionStorage:
タブA(example.com)→ 独自ストレージ
タブB(example.com)→ 独自ストレージ(タブAと別)
→ 同じサイトでも別タブには共有されない
localStorage:
タブA(example.com)→ 共有ストレージ
タブB(example.com)→ 共有ストレージ(タブAと同じ)
→ 同一オリジンなら全タブで共有される
Cookie との比較
| Cookie | sessionStorage | localStorage | |
|---|---|---|---|
| 有効期限 | 設定可能(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 を使う場面
- テーマ・言語などユーザー設定の永続化
- フォームの下書き自動保存
- 最近見た商品・閲覧履歴
Cookie を使う場面
- 認証トークン(HttpOnly・Secure で保護)
- サーバーが読む必要のある値
- ドメイン間で共有が必要な値