Recipe
localStorage Patterns
Reliable client-side persistence with error handling, quota awareness, and structured data.
Safe Wrapper
Always wrap getItem and setItem in try/catch. Private browsing and storage quotas throw synchronous errors.
function safeGet(key, fallback = null) {
try {
const v = localStorage.getItem(key);
return v === null ? fallback : v;
} catch { return fallback; }
}JSON with Schema Guard
Parse with a version field so stale schemas don't corrupt state on deploy.
function readJSON(key, schemaVersion = 1) {
const raw = safeGet(key);
if (!raw) return null;
try {
const parsed = JSON.parse(raw);
return parsed._v === schemaVersion ? parsed : null;
} catch { return null; }
}Quota Eviction
On QuotaExceededError, evict oldest keys by a stored timestamp prefix.
function setWithEvict(key, value) {
try {
localStorage.setItem(key, value);
} catch (e) {
if (e.name === 'QuotaExceededError') {
const keys = Object.keys(localStorage)
.filter(k => k.startsWith('_ts:'))
.sort((a, b) => +localStorage.getItem(a) - +localStorage.getItem(b));
for (const k of keys.slice(0, 5)) localStorage.removeItem(k);
localStorage.setItem(key, value);
}
}
}Meridian tip: Prefix all keys with your app namespace (mdrn:) to avoid collisions with third-party scripts on the same origin.