Browser Storage & Caching

Chapter 5: Browser Storage & Caching

Modern web applications require robust client-side persistence for performance, offline functionality, and state management. This chapter provides a technical reference for the Web Storage API, IndexedDB, and the Cache API, along with engineering standards for storage orchestration.


I. The Storage Hierarchy

Browsers provide a tiered storage architecture. Selecting the correct mechanism is critical for performance and data integrity.

MechanismAPI TypeCapacityLifecycleUse Case
SessionStorageSynchronous~5MBTab sessionTemporary UI state, single-session forms.
LocalStorageSynchronous~5MBPersistentUser preferences, theme settings, offline drafts.
IndexedDBAsynchronousLarge (Disk %)PersistentStructured data, binary blobs, large datasets.
Cache APIAsynchronousLarge (Disk %)PersistentNetwork requests/responses, offline assets (PWA).

Web StorageLocalStorageSessionStorageSynchronous (Blocking)IndexedDBTransactionalNoSQL / BinaryAsynchronous (Non-Blocking)Cache APIRequest/ResponseService WorkerAsset Caching


II. Web Storage (Local & Session)

The Web Storage API provides a simple key-value pair mechanism. Both localStorage and sessionStorage implement the Storage interface.

1. API Reference: Storage Interface

MethodSyntaxReturnDescription
setItem()storage.setItem(k, v)voidAdds or updates a key with a string value.
getItem()storage.getItem(k)string|nullRetrieves the value associated with the key.
removeItem()storage.removeItem(k)voidDeletes the specified key/value pair.
clear()storage.clear()voidDeletes all keys for the current origin.
key()storage.key(index)string|nullReturns the name of the key at the specified index.

2. Multi-Tab Synchronization: The storage Event

localStorage fires an event on other windows/tabs of the same origin when data is modified.

window.addEventListener('storage', (event) => {
  console.log(`Storage Update:`);
  console.log(`Key: ${event.key}`);
  console.log(`Old Value: ${event.oldValue}`);
  console.log(`New Value: ${event.newValue}`);
  console.log(`Source URL: ${event.url}`);
});

Note: This event does not fire in the tab that performed the modification.


III. IndexedDB (Transactional Database)

IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs. It uses indexes to enable high-performance searches.

1. Technical Workflow

  1. Open Database: indexedDB.open(name, version)
  2. Handle Upgrades: Create objectStores in onupgradeneeded.
  3. Start Transaction: Create a transaction (readonly or readwrite).
  4. Perform Operations: add, put, get, delete.

2. Implementation: Robust Storage Pattern

const openDB = (dbName, version) => {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(dbName, version);

    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      if (!db.objectStoreNames.contains('courses')) {
        db.createObjectStore('courses', { keyPath: 'id', autoIncrement: true });
      }
    };

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

const saveCourse = async (course) => {
  const db = await openDB('ApplicationDB', 1);
  const tx = db.transaction('courses', 'readwrite');
  const store = tx.objectStore('courses');
  
  return new Promise((resolve, reject) => {
    const request = store.put(course);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

IV. The Cache API

The Cache API provides a storage mechanism for Request / Response object pairs. It is the core of Service Worker-based offline capabilities.

1. API Reference: Cache Interface

MethodSyntaxReturnDescription
match()cache.match(req)Promise<Response|null>Returns the first matching response.
add()cache.add(url)Promise<void>Fetches the URL and stores the response.
put()cache.put(req, res)Promise<void>Manually stores a specific request/response pair.
delete()cache.delete(req)Promise<boolean>Deletes the matching entry.
keys()cache.keys()Promise<Array<Request>>Returns an array of keys (requests).

V. Storage Management (Quotas & Persistence)

Browsers manage storage through quotas and eviction policies.

1. Estimating Storage

Use navigator.storage.estimate() to determine how much space is available.

if (navigator.storage && navigator.storage.estimate) {
  const { usage, quota } = await navigator.storage.estimate();
  const percentUsed = ((usage / quota) * 100).toFixed(2);
  console.log(`Storage Usage: ${percentUsed}% (${usage} of ${quota} bytes)`);
}

2. Requesting Persistence

By default, storage is "best-effort" (browser can evict if disk is full). You can request "persistent" storage.

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

VI. Core Engineering Standards

1. Performance Mandates

  • Async Preference: Use IndexedDB or Cache API for datasets larger than 1MB. LocalStorage is synchronous and will block the main thread, causing frame drops (jank).
  • JSON Serialization: Always wrap JSON.parse and JSON.stringify in try/catch blocks when accessing Web Storage to prevent crashes on corrupted data.

2. Security Mandates

  • No Secrets: NEVER store PII (Personally Identifiable Information), passwords, or high-privilege JWTs in browser storage. Browser storage is accessible via XSS. Use HttpOnly cookies for session identifiers.
  • Quota Management: Always handle QuotaExceededError gracefully. Implement a cleanup strategy (e.g., LRU cache) for application data.

Storage State: [HEALTHY ]