webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Unelte
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Formulare
  12. 12Date și backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animații
  16. 16Testare
  17. 17Arhitectură
Web · Lecția 6 din 7

Cookie-uri, localStorage, sessionStorage

Cele 5 locuri unde browserul ține date și când îl folosești pe fiecare.

Actualizat

De ce ai nevoie de stocare în browser

HTTP e stateless: serverul nu știe că request-ul 2 vine de la același om ca request-ul 1. Iar o pagină, la reload, pornește de la zero. Ca să „ții minte” ceva — login, coș, preferințe, un formular pe jumătate completat — ai nevoie de un loc unde datele supraviețuiesc.

Browserul îți dă 5 astfel de locuri, fiecare cu alt scop.

Tipurile de stocare — comparație

Cookie localStorage sessionStorage IndexedDB Cache Storage
Ce ține text mic text (cheie → string) text (cheie → string) obiecte, fișiere, blob-uri răspunsuri HTTP întregi
Capacitate ~4 KB / cookie ~5 MB / origin ~5 MB / origin sute de MB+ sute de MB+
Ajunge automat la server da, la fiecare request nu nu nu nu
Cât trăiește până la Max-Age / Expires pentru totdeauna cât trăiește tab-ul pentru totdeauna pentru totdeauna
Vizibil în toate tab-urile toate tab-urile doar tab-ul curent toate tab-urile toate tab-urile
API header / document.cookie sincron sincron asincron asincron
Citibil din JS da, dacă nu e HttpOnly da da da da
Folosește pentru sesiune, auth, id anonim preferințe UI, draft-uri starea unui wizard / formular pe tab date offline, cache mare PWA, offline (service worker)

Toate sunt izolate pe origin: a.ro nu poate citi ce a salvat b.ro.

Un cookie e un text mic pe care serverul îl setează, iar browserul îl trimite automat înapoi la fiecare request spre același site. De aceea e singurul mod prin care serverul te poate recunoaște.

Set-Cookie: session=abc123; Max-Age=2592000; Path=/; HttpOnly; Secure; SameSite=Lax
Atribut Ce face
Max-Age / Expires cât trăiește; fără ele = cookie de sesiune (dispare la închiderea browserului)
HttpOnly JS nu îl poate citi → protecție contra XSS
Secure se trimite doar pe HTTPS
SameSite=Lax nu se trimite la POST-uri venite de pe alte site-uri → protecție contra CSRF
SameSite=Strict nu se trimite deloc de pe alte site-uri
SameSite=None se trimite peste tot (necesită Secure) — pentru integrări între site-uri
Path / Domain pentru ce URL-uri se trimite

Aplicația asta îți setează un cookie vid (httpOnly) — așa îți recunoaște progresul pe server fără cont.

În Next citești cookie-uri cu cookies() din next/headers și le setezi în Server Actions, Route Handlers sau proxy.ts.

localStorage

Stocare cheie → string, permanentă, partajată de toate tab-urile aceluiași site.

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

localStorage.setItem('cart', JSON.stringify(cart))   // obiectele → JSON
const cart = JSON.parse(localStorage.getItem('cart') ?? '[]')
  • Doar stringuri — obiectele trec prin JSON.stringify / JSON.parse.
  • API sincron: blochează thread-ul; nu salva megabytes la fiecare tastă.
  • Când un tab modifică localStorage, celelalte tab-uri primesc evenimentul storage — așa sincronizezi tema între tab-uri.

sessionStorage

Același API ca localStorage, dar datele trăiesc doar cât trăiește tab-ul:

Situație sessionStorage
reîncarci pagina (F5) rămâne
navighezi în alt URL din același tab, apoi înapoi rămâne
deschizi site-ul într-un tab nou gol — fiecare tab are propriul storage
„Duplică tab-ul” se copiază în tab-ul nou, apoi evoluează separat
închizi tab-ul șters
sessionStorage.setItem('checkout-step', '2')
sessionStorage.getItem('checkout-step')       // '2' — doar în tab-ul ăsta

Când e mai bun decât localStorage: un wizard de checkout sau un formular lung pe care îl ai deschis în două tab-uri — fiecare tab își păstrează propriul progres, fără să se calce pe picioare.

IndexedDB

O bază de date în browser: tabele (object stores), indecși, tranzacții, obiecte întregi (nu doar stringuri), fișiere. API-ul e asincron (nu blochează pagina), dar verbos — în practică folosești un wrapper mic:

import { get, set } from 'idb-keyval'
await set('drafts', [{ id: 1, text: '...' }])   // obiecte direct, fără JSON
const drafts = await get('drafts')

Pentru: aplicații offline, cache de date mari, fișiere încărcate de user înainte de upload.

Cache Storage

Păstrează perechi request → response întregi. Îl folosesc service workers ca un site să meargă offline (PWA). Rar îl atingi direct în aplicații obișnuite.

Reguli de siguranță

  • Niciodată token-uri de autentificare în localStorage / sessionStorage — orice script de pe pagină (inclusiv unul injectat prin XSS) le poate citi. Sesiunea stă într-un cookie HttpOnly.
  • Storage-ul poate arunca eroare (mod privat, spațiu plin, site-data blocat) → try/catch în jurul fiecărei citiri / scrieri.
  • Nu există pe server: în Next, citește-l doar în Client Components, după montare (în useEffect sau useSyncExternalStore), altfel ai erori de hidratare.
  • Userul îl poate șterge oricând — nu e o bază de date de încredere pentru date importante.

Cum alegi

Ai nevoie ca... Folosește
serverul să știe cine ești cookie HttpOnly
o preferință să rămână și după restart localStorage
starea să fie separată pe fiecare tab și să dispară la închidere sessionStorage
date mari, obiecte, offline IndexedDB
starea să poată fi trimisă ca link (filtre, pagină) URL, nu storage
date importante, pe orice device server (baza de date)

Pe scurt

  • Cookie = singurul care merge automat la server; HttpOnly; Secure; SameSite=Lax pentru sesiuni.
  • localStorage = permanent, toate tab-urile; sessionStorage = per tab, până la închidere; ambele doar stringuri, sincrone.
  • IndexedDB pentru date mari și offline; niciodată secrete în storage; mereu try/catch.

Surse oficiale

Exerciții

Ți-a fost utilă pagina?

Un click — fără cont.