LocalStorage i Cookies – przechowywanie danych w przeglądarce za pomocą JavaScript

Ten obszerny artykuł omawia dwa podstawowe mechanizmy przechowywania danych po stronie klienta w przeglądarkach: localStorage i ciasteczka (cookies). Choć oba umożliwiają trwałe zapisywanie danych na urządzeniach użytkowników, znacząco różnią się pojemnością, funkcjonalnością, konsekwencjami bezpieczeństwa i właściwymi zastosowaniami. localStorage zapewnia do 5–10 MB pamięci z prostą obsługą par klucz–wartość w JavaScript, podczas gdy ciasteczka mają ograniczenie około 4 KB na ciasteczko i są dostępne po stronie serwera dzięki nagłówkom HTTP. Artykuł analizuje implementacje techniczne, podatności bezpieczeństwa (w tym ataki XSS i CSRF), kwestie wydajności oraz wymogi zgodności z regulacjami prywatności takimi jak RODO (GDPR). Zrozumienie różnic między tymi mechanizmami jest kluczowe, by projektować strategie trwałości danych, które łączą wydajność aplikacji z ochroną prywatności użytkowników.

Podstawy pamięci przeglądarki i kontekst historyczny

Ewolucja możliwości przeglądarek zasadniczo zmieniła sposób, w jaki deweloperzy podchodzą do trwałości danych po stronie klienta. Przed wprowadzeniem nowoczesnych mechanizmów platforma webowa opierała się wyłącznie na ciasteczkach, które powstały w 1994 roku jako obejście bezstanowej natury połączeń HTTP.

Ciasteczka rozwiązały problem utrzymania stanu, pozwalając serwerom wysyłać do przeglądarek niewielkie pliki tekstowe, które były automatycznie dołączane do późniejszych żądań HTTP do tej samej domeny. Dzięki temu możliwe stało się utrzymywanie sesji, preferencji i stanu uwierzytelnienia między żądaniami.

Ciasteczka miały jednak istotne ograniczenia: pojemność rzędu 4 KB na wpis, automatyczne dołączanie do każdego żądania (co zwiększa ruch sieciowy), brak wygodnego wsparcia dla złożonych typów danych i utrudnienia w zarządzaniu z poziomu JavaScript.

Specyfikacja HTML5 wprowadziła Web Storage API (localStorage i sessionStorage), które uniezależnia trwałość danych od wymiany HTTP. Dane Web Storage nie są automatycznie wysyłane na serwer, co daje pełną kontrolę nad transmisją i umożliwia funkcje offline, strategie cache’owania oraz rozbudowane zarządzanie stanem po stronie klienta.

Zrozumienie localStorage – architektura, możliwości i implementacja

localStorage to mechanizm Web Storage API przeznaczony do trwałego przechowywania danych po stronie klienta. Dane utrzymują się między sesjami przeglądarki, także po zamknięciu kart i ponownym uruchomieniu systemu. Przestrzeń localStorage jest powiązana z pochodzeniem (origin: protokół, domena, port) i izolowana zasadą same-origin.

Model localStorage opiera się na parach klucz–wartość, w których zarówno klucze, jak i wartości są przechowywane jako łańcuchy znaków. Aby zapisać obiekty lub tablice, należy użyć serializacji JSON.

Do pracy z localStorage służą cztery podstawowe metody:

  • setItem() – zapis pary klucz–wartość w magazynie;
  • getItem() – odczyt wartości jako string lub zwrot null, gdy klucza brak;
  • removeItem() – usunięcie wskazanej pary;
  • clear() – wyczyszczenie całej przestrzeni localStorage dla originu.

Przykładowe operacje zapisu i odczytu wartości prymitywnych:

localStorage.setItem('username', 'john');
const name = localStorage.getItem('username');

Pojemność localStorage jest znacząco większa niż ciasteczek i zwykle wynosi 5–10 MB na origin. To sprzyja przechowywaniu preferencji, lokalnego cache’u odpowiedzi API, szkiców i innego niesensytywnego stanu utrzymywanego między sesjami.

Operacje localStorage są synchroniczne i blokują wątek główny na czas wykonania. Pojedyncze wywołania są szybkie, lecz bardzo duże zbiory danych mogą opóźniać start aplikacji, ponieważ przeglądarki wczytują zawartość localStorage do pamięci przy ładowaniu strony.

Praktyczna implementacja i wzorce serializacji danych

Proste wartości zapisujemy bezpośrednio: localStorage.setItem('theme', 'dark'). Przy obiektach i tablicach standardem jest JSON.stringify() przed zapisem i JSON.parse() po odczycie.

Przykład zapisu i odczytu obiektu użytkownika:

const user = { name: 'Alice', age: 30, email: '[email protected]' };
localStorage.setItem('currentUser', JSON.stringify(user));

const userData = JSON.parse(localStorage.getItem('currentUser'));

Wiele aplikacji zapisuje kolekcje jako tablice JSON w localStorage (np. listy zadań). Każda zmiana dużej tablicy wymaga ponownej serializacji i zapisu całości, co rośnie kosztowo przy częstych modyfikacjach.

localStorage nie ma natywnego TTL, więc wygaśnięcie danych implementuje się aplikacyjnie. Poniżej uniwersalny wzorzec z polem expiry:

function setWithExpiry(key, value, ttl) {
const item = { value, expiry: Date.now() + ttl };
localStorage.setItem(key, JSON.stringify(item));
}

function getWithExpiry(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const item = JSON.parse(raw);
if (Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}

Zrozumienie ciasteczek (cookies) – mechanizmy, atrybuty i integracja z serwerem

Ciasteczka to odmienny model przechowywania danych w przeglądarce, idealny do uwierzytelniania, zarządzania sesjami i śledzenia. Serwer wysyła ciasteczko w nagłówku Set-Cookie, a przeglądarka automatycznie dołącza je do kolejnych żądań do tej samej domeny w nagłówku Cookie.

Typowy przebieg: po poprawnym logowaniu serwer generuje identyfikator sesji i zwraca go w Set-Cookie. Przeglądarka zapisuje ciasteczko i dołącza je do następnych żądań, co pozwala serwerowi rozpoznawać użytkownika bez ponownego logowania.

Ciasteczka obsługują kluczowe atrybuty bezpieczeństwa i kontroli zakresu:

  • Secure – wysyłanie wyłącznie przez HTTPS;
  • HttpOnly – brak dostępu z poziomu JavaScript (ochrona przed kradzieżą przy XSS);
  • SameSite – kontrola dołączania w żądaniach cross-site (ochrona przed CSRF);
  • Expires/Max-Age – wbudowane mechanizmy wygaśnięcia;
  • Path – ograniczenie zakresu URL w obrębie domeny;
  • Domain – kontrola dostępności w subdomenach.

Ograniczenie pojemności około 4 KB na ciasteczko sprawia, że najlepszym zastosowaniem są małe identyfikatory (np. ID sesji). Automatyczne wysyłanie z każdym żądaniem zwiększa rozmiar transferu, zwłaszcza przy licznych wywołaniach API.

Kompleksowe porównanie – localStorage kontra ciasteczka

Pojemność to najbardziej widoczna różnica: localStorage oferuje 5–10 MB na origin, ciasteczka ~4 KB na wpis. localStorage sprzyja przechowywaniu stanu po stronie klienta (preferencje, cache, dane niesensytywne), podczas gdy ciasteczka ograniczają się do małych identyfikatorów i metadanych, często wymuszając trzymanie stanu po stronie serwera.

Sposób transmisji danych różni się zasadniczo: ciasteczka są automatycznie dołączane do żądań HTTP (ułatwia uwierzytelnianie, ale obciąża sieć), a dane z localStorage nigdy nie są wysyłane automatycznie — aplikacja musi je wysłać jawnie (fetch/XHR).

Poniższa tabela zbiera najważniejsze różnice w czytelnej formie:

Cecha localStorage Ciasteczka
Pojemność 5–10 MB na origin ~4 KB na wpis
Transmisja Nigdy automatycznie, tylko ręcznie (fetch/XHR) Automatycznie z każdym żądaniem do domeny
Wygaśnięcie Brak natywnego TTL (wymaga logiki aplikacji) Wbudowane (sesyjne lub Expires/Max-Age)
Dostęp po stronie serwera Nie Tak (nagłówki HTTP)
Bezpieczeństwo Brak HttpOnly/Secure; podatność na XSS Atrybuty HttpOnly, Secure, SameSite
Zakres/origin Ściśle per origin (protokół, domena, port) Obsługa Domain/Path (możliwe subdomeny)
API Synchroniczne, proste klucz–wartość Zarządzane przez nagłówki + document.cookie
Wpływ na wydajność Wczytywane przy starcie; blokujące operacje Zwiększają każdy request o rozmiar cookie

Implikacje bezpieczeństwa i wektory ataku

Ataki XSS (cross-site scripting) są kluczowym zagrożeniem dla obu mechanizmów. Złośliwy kod JS dziedziczy uprawnienia strony, więc odczyta localStorage i ciasteczka nie-HttpOnly.

localStorage jest szczególnie narażone na XSS, bo nie oferuje atrybutów ograniczających dostęp JavaScript; dane mogą zostać łatwo wyeksfiltrowane.

Ciasteczka oferują lepszą ochronę dzięki HttpOnly i Secure, co utrudnia kradzież ciasteczek uwierzytelniających. Nie eliminuje to jednak całkowicie ryzyka — przeglądarka nadal automatycznie dołącza je do żądań.

CSRF (cross-site request forgery) silniej dotyka ciasteczek z racji automatycznego dołączania do żądań. localStorage nie jest podatne na CSRF, bo nie jest wysyłane automatycznie.

Obrona przed CSRF i XSS powinna obejmować następujące elementy:

  • tokeny CSRF – wymaganie nieprzewidywalnej wartości w żądaniach modyfikujących stan;
  • SameSite – ustawienie atrybutu ciasteczek na Lax lub Strict, by ograniczyć żądania cross-site;
  • HttpOnly i Secure – zabezpieczenie ciasteczek sesyjnych przed odczytem z JS i wymuszenie HTTPS;
  • walidację i sanityzację wejścia – minimalizacja ryzyka XSS u źródła.

sessionStorage – wyspecjalizowany mechanizm przechowywania

sessionStorage działa podobnie do localStorage pod względem pojemności (zwykle ok. 5 MB), API i ograniczeń typów (tylko stringi). Kluczowa różnica to czas życia: dane istnieją wyłącznie w obrębie sesji karty/okna i są usuwane po jej zamknięciu.

Zakres sessionStorage jest per karta/okno, w przeciwieństwie do localStorage współdzielonego między kartami tego samego originu. sessionStorage przetrwa odświeżenie i nawigację wstecz w tej samej karcie.

Typowe zastosowania sessionStorage obejmują:

  • tymczasowy stan aplikacji w bieżącej karcie,
  • przechowywanie danych formularzy w procesach wieloetapowych,
  • tokeny sesyjne wygasające po zamknięciu karty,
  • flagi sterujące nawigacją w trakcie sesji.

Automatyczne wygaśnięcie ogranicza ryzyko w porównaniu z localStorage i zmniejsza powierzchnię ataku po zakończeniu sesji.

Zaawansowane rozwiązania pamięci i alternatywy

Dla potrzeb wykraczających poza możliwości localStorage/sessionStorage dostępny jest IndexedDB — niskopoziomowa, asynchroniczna baza danych do przechowywania dużych ilości danych strukturalnych (obiekty, bloby) z indeksami i transakcjami.

Operacje IndexedDB są asynchroniczne, więc nie blokują głównego wątku; pojemność zwykle sięga setek MB lub więcej (w zależności od przeglądarki i urządzenia).

Cache Storage API jest zoptymalizowane do przechowywania odpowiedzi sieciowych (HTML, CSS, JS, obrazy) w service workerach i PWA. Umożliwia zaawansowane strategie cache’owania i pracę offline, ale skupia się na zasobach sieciowych, a nie dowolnych strukturach danych.

Dla lepszej orientacji w doborze narzędzia warto porównać najważniejsze właściwości:

Mechanizm API Trwałość Pojemność (typowo) Zastosowanie
localStorage Synchroniczne KV Między sesjami 5–10 MB preferencje, lekki cache, szkice
sessionStorage Synchroniczne KV Do zamknięcia karty ~5 MB dane tymczasowe sesji
IndexedDB Asynchroniczne, transakcyjne Trwałe Setki MB+ duże/relacyjne dane, offline
Cache Storage Asynchroniczne (Service Worker) Trwałe Dziesiątki–setki MB+ cache zasobów, PWA

Zgodność regulacyjna i kwestie prywatności

Krajobraz regulacyjny wokół ciasteczek i localStorage stał się bardziej złożony po wejściu w życie RODO (GDPR) i podobnych przepisów. RODO traktuje magazyny przeglądarkowe jako narzędzia zbierania danych osobowych, które mogą wymagać wyraźnej zgody użytkownika (zwłaszcza dla analityki, marketingu, personalizacji).

Wyróżnia się ciasteczka niezbędne (np. sesyjne do logowania), które można stosować bez zgody, oraz nieniezbędne (analityka, reklama), które wymagają zgody. Naruszenia mogą skutkować karami do 4% rocznych przychodów.

Kluczowe elementy wdrożenia zgodności powinny obejmować:

  • transparentne powiadomienia – jasny opis celów, kategorii danych i okresów przechowywania;
  • mechanizmy zgody – możliwość łatwego zaakceptowania/odrzucenia przed użyciem magazynów nieniezbędnych;
  • rejestrowanie i wycofanie – dokumentowanie zgód oraz proste wycofanie z natychmiastowym zaprzestaniem przetwarzania.

Ciasteczka stron trzecich (third‑party) podlegają szczególnej kontroli i są wygaszane przez główne przeglądarki. Safari i Firefox domyślnie je blokują, a Chrome stopniowo je usuwa.

Wydajność i strategie optymalizacji

localStorage ma synchroniczne API, które może blokować główny wątek. Duże zbiory danych wydłużają start, bo zawartość jest wczytywana do pamięci przy ładowaniu strony.

Ciasteczka zwiększają rozmiar każdego żądania HTTP, gdyż są dołączane automatycznie. Nawet małe ciasteczka, przy wielu żądaniach, wpływają na pasmo i czas transferu.

Praktyczne wskazówki optymalizacyjne obejmują:

  • minimalizowanie rozmiaru ciasteczek i liczby domen/subdomen,
  • nieprzechowywanie dużych/często zmienianych struktur w localStorage,
  • w przypadku dużych wolumenów i konieczności indeksowania wybór IndexedDB zamiast localStorage,
  • partycjonowanie i kompresję danych cache’owanych po stronie klienta.

Dobre praktyki i schemat rekomendacji

Poniższy schemat pomaga szybko dobrać właściwy mechanizm do potrzeb:

  • localStorage – niesensytywne dane długoterminowe, które mają przetrwać sesje i nie wymagają dostępu po stronie serwera (preferencje interfejsu, lokalny cache odpowiedzi API, szkice treści);
  • sessionStorage – dane tymczasowe dostępne w bieżącej karcie/oknie, które powinny zniknąć po jej zamknięciu (tokeny sesyjne, dane formularzy w krokowych przepływach, preferencje sesyjne);
  • ciasteczka – zarządzanie sesją i uwierzytelnianiem po stronie serwera; przechowuj w nich identyfikatory sesji lub tokeny ustawione z HttpOnly, Secure i odpowiednim SameSite;
  • bezpieczeństwo – chroń przed XSS (walidacja/sanityzacja wejścia), wdrażaj ochronę przed CSRF (tokeny, SameSite) i nie przechowuj wrażliwych danych w localStorage ani w ciasteczkach bez HttpOnly.

Gdy potrzebujesz znacznej pojemności, złożonych zapytań lub pełnej pracy offline, rozważ IndexedDB (ewentualnie biblioteki ułatwiające API, np. RxDB).

Zarządzanie pamięcią i limity przydziału

Zrozumienie kwot i polityk eksmisji jest kluczowe przy przechowywaniu większych ilości danych. Chrome może wykorzystać do ~80% przestrzeni dyskowej przeglądarki, a pojedynczy origin do ~60%. Firefox pozwala originom w grupie eTLD+1 zużyć do 2 GB. Safari zwykle oferuje około 1 GB i może prosić o zwiększenie w krokach po 200 MB. Tryb incognito/prywatny znacząco obniża limity (np. ~5% w Chrome).

Dla szybkiego porównania limitów w przeglądarkach zobacz poniższe zestawienie:

Przeglądarka Przybliżona kwota Uwagi
Chrome (Chromium) ~80% dysku (origin do ~60%) LRU dla eksmisji; niższe limity w trybie incognito
Firefox do ~2 GB per eTLD+1 Eksmisja zbliżona do LRU
Safari ~1 GB Prośby o zwiększenie w krokach po 200 MB

Aplikacje mogą sprawdzać dostępne zasoby przez StorageManager. Przykładowy kod szacowania zużycia i kwoty:

if (navigator.storage && navigator.storage.estimate) {
const { usage, quota } = await navigator.storage.estimate();
const percentageUsed = Math.round((usage / quota) * 100);
console.log(`Storage used: ${percentageUsed}%`);
}

Gdy pamięć się zapełnia, przeglądarki oparte na Chromium usuwają dane najrzadziej używanych originów (LRU), aż do zwolnienia miejsca; Firefox stosuje podobną strategię. Aplikacje mogą wnioskować o persistent storage, aby zapobiegać automatycznym usunięciom:

if (navigator.storage && navigator.storage.persist) {
const persisted = await navigator.storage.persist();
console.log(`Persistent storage granted: ${persisted}`);
}

Programista i twórca serwisu Creative Coding, absolwent Politechniki Warszawskiej (WEiTI). Od 10+ lat łączy front‑end, grafikę generatywną i narzędzia dla twórców; opublikował 120+ projektów i artykułów, prowadził warsztaty dla 2 000+ uczestników. Pracuje z JavaScriptem, Three.js, P5.js i GLSL, bada wydajność i dokumentuje procesy, tworząc praktyczne przewodniki dla osób łączących kod z obrazem, dźwiękiem i interakcją.
Zostaw komentarz

Komentarze

Brak komentarzy. Dlaczego nie rozpoczniesz dyskusji?

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *