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.
Cookie-uri
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 evenimentulstorage— 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 ăstaCâ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 cookieHttpOnly. - 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
useEffectsauuseSyncExternalStore), 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=Laxpentru 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.