HTML Tutorial 0/139 lessons ~6 min read Lesson 57

    HTML Web Storage

    html web storage web storage persists ux state synchronously per origin — production use is theme web storage — localstorage

    Course progress0%
    Focus
    18 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Web StoragelocalStorage and sessionStorage — provides synchronous key-value persistence per origin. Staff engineers use it for non-sensitive UX prefs (theme, collapsed panels) like Twitter's dark mode — never auth tokens (XSS steals localStorage). Know 5MB quota limits, blocking main-thread JSON.parse of megabyte blobs, and sessionStorage tab-scoped checkout flow pattern used by Amazon.

    Business problem

    Business pressure: Cart persistence, theme, locale, and draft autosave improve conversion — Shopify themes default to localStorage cart backup. Security teams ban JWT in localStorage after every major XSS postmortem (2019–2024 cycle). GDPR: stored IDs may be personal data — disclose in privacy policy.

    • Conversion: Abandoned cart recovery via localStorage — +8% return visits when synced server-side on login.
    • Compliance: PII in storage without consent — regulatory risk; clear on logout required.
    • SEO: Storage invisible to crawlers — SSR must render default state, not empty until JS reads storage.

    Why this feature exists

    Platform motivation: Replace fragile cookies for large client-only state — no automatic HTTP transmission reduces CSRF token leakage surface for purely client prefs. Not a cookie replacement for auth — HttpOnly cookies still win for sessions.

    • History: HTML5 Web Storage (2009) vs WebSQL (dead) vs IndexedDB (structured large data).
    • Alternative rejected: document.cookie for 4KB theme JSON — sent every request, hurts perf.
    • Modern role: Small sync prefs; large/offline → IndexedDB; HTTP cache → Cache API in SW.

    Browser internals

    Inside the engine: Storage lives in per-origin SQLite or LevelDB profile directory — synchronous IPC from renderer main thread on getItem/setItem. Quota ~5MB per origin; throws QuotaExceededError. sessionStorage separate namespace per top-level browsing context — duplicated across tabs only for same tab session. Private browsing may evict or throw — Safari ITP clears after 7 days.

    • Sync: setItem blocks main thread — large JSON parse stalls INP.
    • Events: storage event fires in other tabs on localStorage change — not same tab; cross-tab sync pattern.
    • Clear: User "Clear site data" wipes — apps must rehydrate from server.
    javascript
    try {
    localStorage.setItem('prefs', JSON.stringify({ theme: 'dark' }));
    } catch (e) {
    if (e.name === 'QuotaExceededError') pruneOldKeys();
    }
    window.addEventListener('storage', e => {
    if (e.key === 'prefs') applyTheme(JSON.parse(e.newValue));
    });

    Rendering workflow

    Rendering path: FOUC when theme read late — flash white before dark CSS. Netflix and GitHub inject inline script in head reading localStorage before first paint (blocking but tiny). Alternative: match prefers-color-scheme default, enhance from storage on idle.

    • Critical path: Minimize sync storage reads before paint — <1ms for small keys OK in head.
    • Layout: Sidebar collapsed state from storage — set class on html element early.
    • SSR mismatch: Hydration warning if server HTML doesn't match stored client state — accept or cookie-read server-side for theme.

    Feature deep dive

    localStorage vs sessionStorage: local persists until cleared; session clears tab close. Same API: setItem, getItem, removeItem, clear, key(n), length. Store JSON strings — no native object storage. Version keys: cart:v2 for schema migration.

    • Never store: JWT, refresh tokens, credit cards, health data — HttpOnly cookies or server session.
    • Do store: UI density, last tab, draft form text (non-sensitive), anonymous cart ID pre-login.
    • Feature detect: try/catch — Safari private mode throws on setItem.
    javascript
    const STORAGE_KEY = 'shop-cart:v1';
    function getCart() {
    try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }
    catch { return []; }
    }
    function setCart(items) {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); }
    catch (e) { console.warn('Storage full', e); }
    }

    Accessibility analysis

    A11y architecture: Restored UI state (font size, contrast) from storage can help a11y prefs persist — expose same controls in settings UI, not storage-only. Screen readers don't care about storage — rendered DOM must reflect state with proper roles.

    • Screen readers: Announce cart count update via aria-live when storage sync changes badge.
    • Keyboard: N/A directly — indirect via restored focus targets; don't steal focus on cross-tab storage event.
    • WCAG: User preferences for contrast stored client-side OK if settings UI mirrors.

    SEO impact

    SEO architecture: Googlebot runs JS but fresh context — localStorage empty each fetch. Pages must render meaningful content without stored state. Client-only cart count "0 items" in HTML until JS — fine for cart page, bad for "Your saved articles" as only indexable content.

    • Crawl: SSR user-specific content via cookie session on server — not localStorage.
    • Rich results: Product availability in storage ≠ truth — schema from server.
    • CWV: Megabyte localStorage read on boot — parse in worker if large.

    Security considerations

    Security boundary: XSS reads entire localStorage — game over for tokens stored there. OWASP cheat sheet: session tokens HttpOnly Secure SameSite. CSRF not applicable to storage (not auto-sent). Physical access: storage readable in DevTools — shared computer risk for draft content.

    • XSS: Primary threat — prioritize CSP + sanitization over "encrypting" localStorage (key in JS anyway).
    • Subdomain: storage per origin — a.evil.com cannot read b.com; but all *.app.com share if same origin misconfigured.
    • Clear logout: localStorage.removeItem for user-specific keys on sign-out — banking standard.

    Performance impact

    Performance: Sync API on hot path kills INP — Amazon debounces cart writes 300ms. storage event storm across 10 tabs — debounce apply. QuotaExceeded — prune LRU keys proactively.

    • INP: JSON.stringify entire catalog on every click — move to IndexedDB or server cart.
    • Memory: Duplicating storage in React state — single source of truth pattern.
    • Private mode: Feature detect throw — degrade gracefully without error toast spam.

    Real production example

    Shopify Dawn theme cart pattern: localStorage cart backup when offline/anonymous; merge to server cart on login via API; clear conflicting keys; theme color scheme in localStorage with head inline read for FOUC prevention — documented trade-off.

    • Schema version: Migrate v1→v2 cart shape on read — avoid silent corruption.
    • Cross-tab: storage listener updates mini-cart badge — Amazon header sync.
    • Logout: wipe PII keys list in auth module — centralized clearUserStorage().
    javascript
    // Head inline — GitHub dark mode pattern (simplified)
    const theme = localStorage.getItem('theme');
    if (theme === 'dark') document.documentElement.classList.add('dark');

    Enterprise usage

    Enterprise: IT policy blocks storage on some domains — apps detect and use server-only mode. Healthcare apps avoid localStorage for PHI — session-only encrypted IndexedDB if offline required. SSO timeout clears storage keys tied to user.

    • Design system: useLocalStorage hook with schema, version, quota handler, SSR safe default.
    • Audit: grep codebase for localStorage.setItem — security review flagged tokens.
    • CI: Unit tests mock Storage with jest-localstorage-mock.

    Common production failures

    What breaks in prod: JWT in localStorage + XSS = breach; Safari private mode throw uncaught; 5MB cart with product catalog cached; hydration mismatch flash; forgot clear on logout on shared PC.

    • Security: npm supply chain XSS stole thousands of localStorage refresh tokens — incident response playbook now mandates HttpOnly.
    • Safari: Private browsing setItem throw — checkout "broken" until try/catch added.
    • Perf: 2MB JSON parse on main thread every navigation — moved to IndexedDB + async.

    Architecture review questions

    • Are auth tokens absent from localStorage?
    • Is there try/catch for QuotaExceeded and private mode?
    • Does SSR render before client storage read?
    • Are keys versioned for schema migration?
    • Is user data cleared on logout?
    • Are large payloads in IndexedDB instead?

    Hands-on project

    Project: Theme + cart persistence: versioned keys, quota handling, cross-tab sync, logout clear, inline head script for theme FOUC, no auth tokens.

    • Deliverable: storage event updates badge; SSR-safe defaults documented.
    • Verify: Private mode graceful; security review checklist passed.
    • Stretch: Migrate cart to IndexedDB when item count >100.

    Interview questions

    localStorage vs HttpOnly cookie for session?(Advanced)

    HttpOnly cookie for auth — not readable by JS, CSRF mitigated with SameSite. localStorage any XSS steals it. Cookies auto-sent — use for server session ID only, not fat payloads.

    Follow-up: SameSite=None when?

    How do you sync state across tabs?(Advanced)

    storage event on window for localStorage changes from other tabs. Same tab doesn't fire — use custom BroadcastChannel for same-tab components. Debounce handler.

    Follow-up: BroadcastChannel vs storage event?

    Fix dark mode FOUC with storage.(Advanced)

    Inline blocking script in head reads theme key and sets class on html before body parse — or default to prefers-color-scheme until enhanced. Accept brief mismatch vs blocking script trade-off.

    Follow-up: SSR cookie for theme?

    Try it yourself

    Edit the HTML, CSS, or JS panels — the preview updates as you type.

    Try it yourself

    Preview

    Summary

    Web Storage persists UX state synchronously per origin — production use is theme/cart prefs with schema versioning, XSS-aware token exclusion, cross-tab storage events, and IndexedDB handoff when data grows beyond safe sync sizes.

    Ready to mark this lesson complete?Track your journey across the entire course.