eduardweb.
Hooks & PatternsÎncepător#nextjs#react#hooks#react-patterns

Cum scrii un useLocalStorage pentru Next.js fără erori de hydration

De Liliana Ghiță, 31 mai 2026 · 5 vizualizări · 3 like-uri

Postat 31 mai 2026
typescript
import { useState, useEffect } from 'react';

export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] {
  const [storedValue, setStoredValue] = useState<T>(initialValue);

  // Citim valoarea reală din browser doar după ce componenta s-a montat
  useEffect(() => {
    try {
      const item = window.localStorage.getItem(key);
      if (item) {
        setStoredValue(JSON.parse(item));
      }
    } catch (error) {
      console.warn(`Eroare la citirea cheii "${key}" din localStorage:`, error);
    }
  }, [key]);

  // Funcția de salvare care actualizează și starea și storage-ul
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.warn(`Eroare la scrierea cheii "${key}" în localStorage:`, error);
    }
  };

  return [storedValue, setValue];
}

Am pățit-o acum vreo doi ani la un proiect cu peste 15.000 de utilizatori activi, când am trecut de la un SPA chior la Next.js. Am pus un hook clasic de useLocalStorage pentru a ține minte preferințele de temă (dark/light mode) și m-am trezit cu consola plină de erori roșii de hydration mismatch. Clientul vedea pentru o fracțiune de secundă tema albă, apoi ecranul sărea brusc pe cea neagră.

Problema e simplă, dar extrem de enervantă. Serverul de Next.js sau Remix nu are habar ce ai tu salvat în browser. El randează pagina folosind valoarea de fallback (să zicem, "light"). Când codul ajunge în browser, React încearcă să facă "hydration" (să lipească event listenerii peste HTML-ul primit). Dacă în localStorage ai "dark", React observă imediat că HTML-ul generat pe client diferă de cel de pe server și începe să urle în consolă.

De ce crapă codul clasic în SSR?

Când Next.js face build-ul paginii, rulează codul tău React pe server (în Node.js). Node.js nu are obiectul window și nici localStorage. Dacă încerci să scrii const [state, setState] = useState(localStorage.getItem('key')), codul va crăpa direct la build sau la primul request cu eroarea ReferenceError: window is not defined.

Chiar dacă pui o verificare rapidă de genul typeof window !== 'undefined', tot nu scapi de hydration mismatch. HTML-ul generat inițial pe server tot va fi diferit de cel pe care vrea React să îl randeze prima oară în browser.

Soluția: Two-pass rendering

Soluția pe care o folosesc acum se bazează pe un principiu simplu: ne asigurăm că prima randare pe client este identică cu cea de pe server. Abia după ce componenta s-a montat (adică suntem 100% siguri că rulăm doar în browser), citim din localStorage și actualizăm starea.

Am pus în secțiunea de mai jos varianta simplificată, dar complet funcțională, pe care o poți copia direct în proiectul tău.

Trade-off-ul sincer: Layout Shift

Nimic nu e gratis în web development. Folosind această abordare, ai un mic compromis de făcut.

Dacă utilizatorul are setat "dark mode" în storage, dar valoarea ta inițială de pe server este "light", el va vedea pentru câteva milisecunde versiunea "light" până se montează componenta și se face update-ul stării. Se numește flash of unstyled content (FOUC).

Dacă vrei să eviți asta complet pentru chestii critice ca temele vizuale, singura soluție reală este să folosești cookies trimise la server sau un script mic de blocare injectat în <head>-ul paginii, nu localStorage. Pentru orice altceva (filtre, date din formulare, preferințe secundare), hook-ul propus își face treaba perfect și te scapă de erori.

Un mic gotcha la final

Dacă ai aceeași stare în două componente diferite din pagină, ele nu se vor sincroniza automat între ele doar prin acest hook dacă una dintre ele modifică valoarea în storage. Pentru cazuri mai complexe de sincronizare în timp real între tab-uri sau componente, va trebui să asculți de evenimentul storage al browserului. Pentru cazuri simple însă, varianta asta e curată și sigură.

Voi cum gestionați starea persistată în Next.js? Mergeți pe cookies ca să le aveți direct pe server sau folosiți abordarea asta cu delay pe client?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.