localStorage
Persistent, synchronous key-value storage built into every browser.
Overview
localStorage is a browser API that lets a website store key-value string pairs in the user's browser, with no expiration date. Unlike a cookie, nothing stored in it is ever sent to the server automatically — it exists purely on the client, scoped to the page's origin (protocol + domain + port).
Why It Exists
Before localStorage (part of the Web Storage API, standardized in HTML5), the only client-side persistence mechanism was cookies — and cookies were never designed for this job. Every cookie is sent with every HTTP request to the same domain, so using them for anything beyond a few small tokens meant paying a bandwidth and latency cost on every request, for data the server usually didn't even need. localStorage solves this by keeping data entirely on the client, with a much larger capacity, and zero network overhead.
Real World Example
A theme toggle (light/dark mode) is the canonical example: the user's preference is saved to localStorage on click, and read back on every page load — before React or any framework even hydrates — so the correct theme applies instantly with no flash of the wrong theme. Shopping cart drafts, unsaved form data, and "don't show this again" dismissible banners are other common uses.
How It Works
Every origin gets its own isolated localStorage instance, exposed on `window.localStorage`. It stores everything as strings — objects and arrays must be serialized with `JSON.stringify()` before storing and parsed back with `JSON.parse()` after reading. All operations are synchronous, which is simple to use but means large reads/writes can briefly block the main thread.
Diagram
How data flows between a page and localStorage — entirely client-side
Page calls setItem('key', 'value')
Browser writes to disk
the origin's local storage partition
Data persists
across tabs, page reloads, and browser restarts
Page calls getItem('key')
reads it back — no network request involved
Syntax
// Write
localStorage.setItem("theme", "dark");
// Read
const theme = localStorage.getItem("theme"); // "dark" | null
// Remove one key
localStorage.removeItem("theme");
// Remove everything for this origin
localStorage.clear();Methods
setItem(key, value)Stores a key-value pair. Both are coerced to strings.
getItem(key)Returns the string value, or null if the key doesn't exist.
removeItem(key)Deletes a single key-value pair.
clear()Deletes every key-value pair for the current origin.
key(index)Returns the name of the key at a given numeric index — rarely used directly.
Common Mistakes
Calling localStorage during server-side rendering
Why: `window` doesn't exist on the server. In a Next.js Server Component, or during the first SSR pass of a Client Component, accessing `localStorage` throws a ReferenceError.
Fix: Only read/write localStorage inside a `useEffect` (which runs client-side only) or behind a `typeof window !== "undefined"` guard.
Storing sensitive data (tokens, PII) in localStorage
Why: Any JavaScript running on the page — including injected via an XSS vulnerability — has full read/write access to localStorage. There's no HttpOnly-style protection like cookies have.
Fix: Keep auth tokens in memory or an HttpOnly cookie instead; use localStorage only for non-sensitive UI state.
Not handling the storage quota limit
Why: Most browsers cap localStorage around 5–10MB per origin. Exceeding it throws a QuotaExceededError that, if unhandled, can silently break the feature.
Fix: Wrap writes in a try/catch, and treat localStorage as best-effort, not guaranteed storage.
Forgetting JSON.stringify / JSON.parse
Why: localStorage only stores strings. Storing an object directly silently stores the string "[object Object]" instead of the actual data.
Fix: Always serialize on write and parse on read, and guard the parse in a try/catch in case the stored value is malformed.
Interview Questions
What's the difference between localStorage and sessionStorage?
Both share the same API. localStorage persists indefinitely (until explicitly cleared); sessionStorage is cleared when the tab is closed and isn't shared between tabs, even for the same origin.
Why doesn't localStorage work during server-side rendering, and how do you fix it in a framework like Next.js?
`localStorage` is a `window` property, and `window` doesn't exist in a Node.js server environment. Access it only inside a `useEffect` hook or a `typeof window !== "undefined"` guard so it only runs after the component mounts in the browser.
Why is localStorage considered unsafe for storing authentication tokens, and what's the alternative?
localStorage has no origin-level protection against script access — any XSS vulnerability on the page can read every key and exfiltrate it. HttpOnly cookies are inaccessible to JavaScript entirely, which removes that specific attack vector (though they introduce CSRF considerations that need their own mitigation, like SameSite cookies and CSRF tokens).
Production Best Practices
Do
✓Wrap every localStorage call in a try/catch — quota errors and disabled storage (private browsing in some browsers) both throw.
✓Namespace your keys (e.g. `biztechlab:theme`) to avoid collisions with other scripts on the same origin.
✓Treat it as a cache, not a database — always have a fallback if the value is missing or corrupted.
Don't
✗Don't store auth tokens, passwords, or personal data in it.
✗Don't assume it's available — private browsing modes and some browser settings disable it entirely.
✗Don't store large payloads — it's synchronous, so big reads/writes can jank the UI.
Comparison
| localStorage | sessionStorage | Cookies | IndexedDB | |
|---|---|---|---|---|
| Persistence | Until cleared | Until tab closes | Configurable expiry | Until cleared |
| Capacity | ~5–10MB | ~5–10MB | ~4KB | Hundreds of MB+ |
| Sent with HTTP requests | No | No | Yes, automatically | No |
| API style | Synchronous | Synchronous | String parsing | Asynchronous |
| Best for | Small UI state | Per-tab state | Server-readable tokens | Structured/large data |