webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
Web · Lesson 6 of 7

Cookies, localStorage, sessionStorage

The 5 places the browser keeps data and when to use each.

Updated

Why you need storage in the browser

HTTP is stateless: the server doesn't know that request 2 comes from the same person as request 1. And a page starts from scratch on every reload. To "remember" anything — login, cart, preferences, a half-filled form — you need a place where data survives.

The browser gives you 5 such places, each with a different purpose.

Storage types compared

Cookie localStorage sessionStorage IndexedDB Cache Storage
Holds small text text (key → string) text (key → string) objects, files, blobs whole HTTP responses
Capacity ~4 KB / cookie ~5 MB / origin ~5 MB / origin hundreds of MB+ hundreds of MB+
Sent to the server automatically yes, on every request no no no no
Lifetime until Max-Age / Expires forever as long as the tab lives forever forever
Visible in all tabs all tabs only the current tab all tabs all tabs
API header / document.cookie synchronous synchronous asynchronous asynchronous
Readable from JS yes, unless HttpOnly yes yes yes yes
Use for session, auth, anonymous id UI preferences, drafts wizard / form state per tab offline data, large cache PWA, offline (service worker)

All of them are isolated per origin: a.com can't read what b.com saved.

Cookies

A cookie is a small piece of text the server sets, and the browser sends back automatically on every request to the same site. That's why it's the only way the server can recognize you.

Set-Cookie: session=abc123; Max-Age=2592000; Path=/; HttpOnly; Secure; SameSite=Lax
Attribute What it does
Max-Age / Expires how long it lives; without them it's a session cookie (gone when the browser closes)
HttpOnly JS can't read it → protection against XSS
Secure sent over HTTPS only
SameSite=Lax not sent with POSTs coming from other sites → protection against CSRF
SameSite=Strict never sent from other sites
SameSite=None sent everywhere (requires Secure) — for cross-site integrations
Path / Domain which URLs it's sent to

This app sets a vid cookie (httpOnly) — that's how it recognizes your progress on the server without an account.

In Next you read cookies with cookies() from next/headers and set them in Server Actions, Route Handlers or proxy.ts.

localStorage

Key → string storage, permanent, shared by all tabs of the same site.

localStorage.setItem('theme', 'dark')
localStorage.getItem('theme')                 // 'dark'
localStorage.removeItem('theme')

localStorage.setItem('cart', JSON.stringify(cart))   // objects → JSON
const cart = JSON.parse(localStorage.getItem('cart') ?? '[]')
  • Strings only — objects go through JSON.stringify / JSON.parse.
  • Synchronous API: it blocks the thread; don't save megabytes on every keystroke.
  • When one tab changes localStorage, the other tabs receive a storage event — that's how you sync the theme across tabs.

sessionStorage

The same API as localStorage, but data lives only as long as the tab:

Situation sessionStorage
reload the page (F5) kept
navigate to another URL in the same tab, then back kept
open the site in a new tab empty — each tab has its own storage
"Duplicate tab" copied into the new tab, then they diverge
close the tab deleted
sessionStorage.setItem('checkout-step', '2')
sessionStorage.getItem('checkout-step')       // '2' — only in this tab

When it beats localStorage: a checkout wizard or a long form open in two tabs — each tab keeps its own progress without stepping on the other.

IndexedDB

A database in the browser: tables (object stores), indexes, transactions, whole objects (not just strings), files. The API is asynchronous (doesn't block the page) but verbose — in practice you use a small wrapper:

import { get, set } from 'idb-keyval'
await set('drafts', [{ id: 1, text: '...' }])   // objects directly, no JSON
const drafts = await get('drafts')

For: offline apps, large data caches, user files before upload.

Cache Storage

Stores whole request → response pairs. Service workers use it to make a site work offline (PWA). You rarely touch it directly in regular apps.

Safety rules

  • Never put auth tokens in localStorage / sessionStorage — any script on the page (including one injected through XSS) can read them. The session lives in an HttpOnly cookie.
  • Storage can throw (private mode, quota exceeded, site data blocked) → wrap every read / write in try/catch.
  • It doesn't exist on the server: in Next, read it only in Client Components after mounting (in useEffect or useSyncExternalStore), otherwise you get hydration errors.
  • The user can clear it at any time — it's not a reliable database for important data.

How to choose

You need... Use
the server to know who you are an HttpOnly cookie
a preference to survive restarts localStorage
state separate per tab that disappears on close sessionStorage
large data, objects, offline IndexedDB
state that can be shared as a link (filters, page) the URL, not storage
important data, on any device the server (database)

Summary

  • A cookie is the only one sent to the server automatically; HttpOnly; Secure; SameSite=Lax for sessions.
  • localStorage = permanent, all tabs; sessionStorage = per tab, until closed; both strings only, synchronous.
  • IndexedDB for large and offline data; never secrets in storage; always try/catch.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.