Frontend Development4 min read

Browser Storage Patterns

Choose the right browser storage: localStorage, sessionStorage, IndexedDB with Dexie, cookies, Cache API, and Origin Private File System.

indexeddbstoragebrowseroffline

Browser storage has evolved beyond localStorage - IndexedDB provides structured, queryable storage; the Cache API stores request/response pairs for offline; the Origin Private File System (OPFS) provides file-system-like access. Choosing the right storage type avoids performance problems and privacy risks.

Default to IndexedDB via Dexie for structured data. Use localStorage only for simple, small, synchronous key-value pairs. Never store sensitive data in localStorage (XSS accessible). Use the Cache API for PWA asset caching (via Workbox). Prefer sessionStorage over localStorage for session-scoped data.

Workflow

  1. Choose the right storage mechanismEach storage type has different characteristics - wrong choice leads to performance or privacy issues.
  2. Use IndexedDB with DexieRaw IndexedDB API is verbose and callback-based - Dexie provides a clean Promise API.
  3. Secure localStorage usagelocalStorage is accessible to any JS on the page - XSS can steal its contents.
  4. Use the Origin Private File SystemOPFS provides file-system-like access with high performance for large binary files - no quota limits.
  5. Estimate and manage storage quotaBrowsers limit storage per origin - exceeding quota causes data loss silently in some browsers.

Output Format

Dexie for IndexedDB with typed schema and queries, localStorage only for non-sensitive preferences, httpOnly cookies for auth, OPFS for large binary data, `navigator.storage.persist()` for important data.

Examples

Dexie schema: `const db = new Dexie("AppDB"); db.version(1).stores({ drafts: "++id, type, updatedAt, &[userId+type]", media: "++id, *tags, size" }); // * = multiEntry index; & = unique compound; ++ = autoincrement`
Storage estimate: `async function checkStorage() { const { usage, quota } = await navigator.storage.estimate(); const pct = Math.round(usage! / quota! * 100); if (pct > 80) { toast.warning(`Storage ${pct}% full - clear some data`) }; return { usedMB: Math.round(usage! / 1e6), quotaMB: Math.round(quota! / 1e6) } }`
OPFS SQLite: `// wa-sqlite with OPFS backend const sqlite3 = await initSQLite({ locateFile: f => `/wasm/${f}` }); const db = await sqlite3.open_v2("app.db", SQLite.SQLITE_OPEN_CREATE | SQLite.SQLITE_OPEN_READWRITE, "multiplied-opfs"); // uses OPFS for persistence`

Edge Cases & Gotchas

  • Safari private mode: localStorage and IndexedDB in Safari private browsing mode are scoped to the session and cleared on close. Detect: `try { localStorage.setItem("_t","1"); localStorage.removeItem("_t") } catch (e) { showPrivateModeWarning() }`
  • Cross-tab synchronization: IndexedDB changes are not automatically reflected across tabs. Use `BroadcastChannel` or storage events to notify other tabs: `channel.postMessage({ type: "db:updated", table: "products" })`
  • Quota exceeded errors: `IDBRequest.onerror` with `QuotaExceededError` - catch this and inform the user to clear space, or implement LRU eviction of old cache entries

When to Break These Rules

  • For data that needs server-side access, real-time sync, or multi-device availability, client-side storage is insufficient - use a backend with sync (Supabase real-time, Firebase, or custom). Browser storage is local-only.

Reference Files

  • Dexie docs: https://dexie.org/
  • OPFS: https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system
  • Storage API: https://developer.mozilla.org/en-US/docs/Web/API/Storage_API
  • web.dev storage: https://web.dev/articles/storage-for-the-web

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.