44. localStorage and sessionStorage: API, quotas, pitfalls
Both are part of the Web Storage API, sharing the same simple key-value string interface, and are NOT sent with HTTP requests (unlike cookies) — purely client-side, only ever read by JS explicitly.
localStorage.setItem("theme", "dark"); // persists across tabs, browser restarts, forever
localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // wipes EVERYTHING for this origin
sessionStorage.setItem("draft", "hello"); // scoped to ONE TAB, cleared when that tab closes
// (a NEW tab to the same site, even same URL,
// gets a fresh, empty sessionStorage)
Real gotchas engineers hit repeatedly:
- Synchronous, main-thread blocking: every
getItem/setItemcall is synchronous disk I/O on the main thread — storing large values or calling it in a hot loop measurably janks the page. There is no async Web Storage API; if you need async, that's a signal to use IndexedDB (§45) instead. - Strings only:
setItemsilently coerces non-strings viaString(value)— storing an object withoutJSON.stringifyfirst stores the useless literal string"[object Object]". - ~5-10MB quota per origin (varies by browser), and exceeding it throws
a
QuotaExceededErroryou must catch explicitly. - Not available in all contexts: throws in some sandboxed iframes and
in Safari's Private Browsing mode (historically silently no-oped, now
throws) — always wrap in
try/catchfor a library meant to run broadly. - Never store secrets in plaintext: any XSS on the page can read
localStoragedirectly — see §47 for what this specifically does and doesn't rule out.
function safeSetItem(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (err) {
if (err.name === "QuotaExceededError") console.warn("Storage full");
return false;
}
}