Guard browser storage
Version keys, store little, guard every call, and cache reads in memory.
From vercel-react-best-practices: client-localstorage-schema and js-cache-storage.
Ship v2 without losing prefs
Set v1 prefs, then press Deploy v2.
One unversioned keyBad
localStorage
getItem('userConfig')Versioned + migrate()Good
localStorage
getItem(`userConfig:${VERSION}`)const VERSION = 'v2'
function migrate() {
try {
const v1 = localStorage.getItem('userConfig:v1')
if (!v1) return
const old = JSON.parse(v1)
saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })
localStorage.removeItem('userConfig:v1')
} catch {}
}
Storage is I/O that throws
Pick a failure mode, then flip the switches.
UnguardedBad
JSON.parse(localStorage.getItem(key))try/catch + defaultsGood
try { … } catch { return null }function loadPrefs() {
try {
const raw = localStorage.getItem('prefs:v1')
return raw ? { ...DEFAULTS, ...JSON.parse(raw) } : DEFAULTS
} catch {
return DEFAULTS
}
}
Persist only what the UI reads
Pick fields, then run the injected script.
Whole user objectBad
setItem('user', JSON.stringify(user))Only what the UI readsGood
setItem('prefs:v1', JSON.stringify(picked))localStorage.setItem('prefs:v1', JSON.stringify({
theme: user.preferences.theme,
notifications: user.preferences.notifications,
}))
Why it matters, pros and cons
Why it matters
Stored data outlives its code, and storage is sync I/O that can throw.
Pros
- Schema changes keep saved prefs.
- Blocked storage degrades to defaults.
- XSS finds a theme, not a token.
Cons
- Every version bump owes a migration.
- An empty catch hides real bugs; report failures.
- A read cache needs invalidation on the storage event.