localStorage vs sessionStorage vs Cookies: Where Should You Store It?
Three ways to store data in the browser, with different lifetimes, sizes and security properties. What each one is for, why login tokens shouldn't go in localStorage, the size limits, and a simple rule for choosing.
Browsers give your app three main places to keep small bits of data on the user's device. They look similar but behave very differently — and choosing wrong for login tokens is a real security mistake.
The quick comparison
| localStorage | sessionStorage | Cookies | |
|---|---|---|---|
| Lasts | Until cleared | Until the tab closes | Until its expiry date (or browser close) |
| Shared between tabs | Yes (same site) | No, per tab | Yes |
| Sent to the server | No | No | Yes, with every request |
| Size | ~5–10 MB | ~5 MB | ~4 KB each |
| Readable by JavaScript | Yes | Yes | Yes, unless HttpOnly |
| Best for | Preferences, drafts | Temporary per-tab state | Login sessions |
localStorage
Simple key–value storage that survives closing the browser:
localStorage.setItem('theme', 'dark')
localStorage.getItem('theme') // "dark"
localStorage.removeItem('theme')
Values are always strings. For objects, use JSON:
localStorage.setItem('draft', JSON.stringify({ title, body }))
const draft = JSON.parse(localStorage.getItem('draft') ?? 'null')
Good for: theme (dark mode), dismissed banners, unsent drafts, UI preferences.
Not for: anything secret. Any JavaScript running on your page — including a third-party script or an XSS injection — can read all of it.
sessionStorage
The same API, but scoped to one tab and wiped when that tab closes. Opening the same site in a new tab starts empty.
Good for: multi-step form progress, "scroll position on this tab," one-off state you don't want leaking between tabs.
Cookies
Small pieces of data the server usually sets, which the browser automatically sends back with every request to that site. That's what makes them the natural fit for login sessions: the server sets a session cookie, and every subsequent request proves who you are. (What is a cookie?)
The security flags are the important part:
| Flag | Does |
|---|---|
HttpOnly |
JavaScript cannot read the cookie — XSS can't steal it |
Secure |
Only sent over HTTPS |
SameSite=Lax / Strict |
Not sent on most cross-site requests — helps against CSRF |
Max-Age / Expires |
How long it lasts |
Where should login tokens go?
This is the question that matters. The safest common answer for web apps:
An HttpOnly, Secure, SameSite cookie.
Why not localStorage? Because if an attacker ever gets JavaScript onto your page — a compromised npm package, an XSS bug, a malicious browser extension — they can read every token in localStorage and use it from anywhere. An HttpOnly cookie can't be read by JavaScript at all.
Many AI-generated apps (and tutorials) store JWTs in localStorage because it's easy. It works, but it's the weaker choice. (Session cookies vs JWTs)
Auth libraries and services such as Supabase have their own defaults; read their docs on server-side/cookie-based sessions if you're rendering on the server.
Things that catch people out
- Server-side rendering can't read localStorage. It only exists in the browser. Code like
localStorage.getItem()running on the server crashes with "localStorage is not defined" — or causes hydration errors. Read it insideuseEffector after checkingtypeof window !== 'undefined'. - Private browsing may clear storage at the end of the session, and some browsers expire script-written storage after a period of no visits.
- Storage can throw. When full, or when blocked by browser settings. Wrap writes in
try/catch. - Cookies add weight to every request. Don't store large data in them.
- Privacy rules. Non-essential cookies and similar storage may need consent in the EU. (Cookie consent banners)
The simple rule
- Logged-in session →
HttpOnlycookie. - Preference or draft that should survive restarts → localStorage.
- Temporary state for one tab → sessionStorage.
- Anything important → your database, not the browser.
EasySpawn runs your app's backend and database alongside the frontend, so sessions can live in secure server-set cookies and important data in Postgres, not the browser. See how it works or join the waitlist.
Related: What Is a Cookie? · Session Cookies vs JWTs · What Is a JWT? · XSS Explained
Keep reading
What Is TypeScript? JavaScript With Labels, Explained for Beginners
TypeScript is JavaScript plus types: labels that say what kind of value each thing holds, checked before your code runs. What it looks like, why AI tools default to it, what .ts and .tsx files are, and how to read the red squiggles.
What Is Cloudflare? What It Does When You Put Your Site Behind It
Cloudflare sits between your visitors and your server: DNS, a CDN, free SSL, DDoS protection and a firewall. What changes when you turn on the orange cloud, what it costs, what it can break, and whether a small app needs it.