This post isn’t about caching strategies like cache-aside or cache-through.
It’s about storage — where can I keep my cache in the browser, and what trade-offs come with each option?
Why does this matter?
Because where you store the cache affects your app’s architecture, performance, and security. If you’re designing a new system, this choice can even shape how your frontend interacts with the backend.
Cache Storage Characteristics
To make the options easier to compare, let’s define key characteristics as questions:
-
Persistence — Can the data survive page reloads or browser restarts?
-
Sync vs. async — Does the API return values directly or via
Promise? -
Capacity — How much data can we store before hitting limits?
-
Security — How exposed is the data to JavaScript or potential attacks?
-
Shared across tabs — Can multiple tabs see the same cache?
-
Isolated per tab — Is each tab sandboxed with its own cache?
-
Performance — How fast are reads and writes under normal load?
-
API accessibility — From where in the runtime can the API be used?
-
Offline availability — Is the data available without a network connection?
Cache Storage Options
In-Memory Cache
The simplest option is an in-memory cache — data stored in a JavaScript Map (or WeakMap) that will be empty when the tab reloads.
You can store any structure — objects, classes, arrays — and you can control expiration manually.
Check out example:
class MemoryCache { constructor({ ttlMs = 60_000 } = {}) { this
In-Memory Cache in a Web Worker
This is similar to the previous approach but runs inside a Web Worker, which isolates cache memory from the main thread.
It’s like a locked drawer managed by an assistant — main scripts can’t access directly inside; they have to ask via messages.
However, this isn’t full protection against XSS. If an attacker can run JavaScript in your page, they can still message the worker and steal data unless you authenticate requests.
// cache.worker.js const cache = new MemoryCache(); self.onmessage = ({ data }) => {
// main thread const worker = new Worker(new URL('./cache.worker.js', import.meta.url), { type
In-Memory Cache in a Shared Worker
A Shared Worker extends the Web Worker idea by allowing multiple tabs (from the same origin) to share one worker and its memory.
It’s like a hallway locker that several rooms can access asynchronously.
Shared Workers are not supported in all browsers. Consider BroadcastChannel or Service Workers as fallbacks.
// shared-worker.js const cache = new MemoryCache(); onconnect = event => { const port = event
// main thread (any tab) const worker = new SharedWorker(new URL('./cache.shared.js', import.meta.url), { type:
Local Storage
Local Storage is like a sticky-note pad — small, simple, and persistent between reloads, but visible to any script on the same site.
-
Data limit: ~5–10 MB (varies by browser).
-
Keys/values: strings only.
-
Scope: per origin (not shared between different sites).
-
Security: accessible by any script on the same origin. Never store secrets here.
class LocalStorageCache { constructor({ prefix = 'cache:', ttlMs = 60_000 } =
Use this for low-volume, non-sensitive, global data such as user preferences.
Session Storage
Session Storage works like Local Storage but with two key differences:
-
Data is wiped when the tab or browser closes (short-lived).
-
Each tab gets its own isolated copy.
It’s good for temporary data (form state, navigation cache) but still vulnerable to XSS.
Cache API
The Cache API is a persistent, async key–value store designed for HTTP requests and responses.
Think of it as a pantry for packaged goods — great for caching network assets, but not ideal for arbitrary data.
-
Persistence: On disk; survives reloads and restarts.
-
API shape: Promise-based. Works with
RequestandResponseobjects. -
Capacity: Large, browser-managed (may evict under pressure).
-
Security: Same-origin; avoid storing secrets in plain text.
-
Shared across tabs: Yes, per origin.
-
Best for: Offline assets, prefetching, API response caching.
// Minimal Cache API wrapper with stale-while-revalidate async function cacheFetch(request, { cacheName = 'app-v1', revalidate = true } = {}
Further reading: MDN Cache API
IndexedDB
IndexedDB is the browser’s built-in NoSQL database — powerful, persistent, and async.
Imagine a warehouse with labeled shelves — slower to reach but can hold huge, organized collections.
-
Persistence: On disk; survives reloads and restarts.
-
API shape: Promise-based (with wrappers).
-
Capacity: Tens to hundreds of MBs, depending on browser and user settings.
-
Security: Same-origin; still accessible to any same-origin script.
-
Shared across tabs: Yes.
-
Use cases: Large datasets, offline apps, queues, sync replicas.
// idb.js — minimal helper function openDB(name, version, upgrade) { return new Promise(
Browser Cache Options — Comparison Table
| Characteristic | In-Memory (Tab) | Web Worker | Shared Worker | Session Storage | Local Storage | Cache API | IndexedDB |
|---|---|---|---|---|---|---|---|
| Persistence | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
| API Sync? | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
| Shared Across Tabs | ❌ | ❌ | ✅ | ❌ | ✅ (per origin) | ✅ | ✅ |
| Capacity | RAM-bound | RAM-bound | RAM-bound | Small | ~5–10 MB | Medium/Large | Large |
| Relative Speed | ★★★ | ★★★ | ★★ | ★★ | ★★ | ★★ | ★ |
| Security Notes | Origin JS access | Needs API auth | Needs API auth | Same | Same | Avoid secrets | Avoid secrets |
| Offline Availability | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
| Best For | Hot ephemeral data | Isolated hot cache | Multi-tab ephemeral cache | Per-tab temp data | Small settings | HTTP/asset cache | Complex/large data |
Choosing the Right One
-
Just need fast temporary lookups in one tab? → In-memory.
-
Want isolation from the main thread? → Web Worker.
-
Need multi-tab sharing? → Shared Worker or
BroadcastChannel. -
Small, non-sensitive persistent data? → Local Storage.
-
Short-lived tab data? → Session Storage.
-
Cache fetch responses or assets offline? → Cache API.
-
Need structured, large, or relational data offline? → IndexedDB.
Each browser storage mechanism is a trade-off between speed, persistence, and isolation.
Think of them as layers:
-
RAM caches (fast, temporary)
-
Web/Shared Workers (isolated, async, optional sharing)
-
Web Storage (Local/Session) (simple, limited)
-
Cache API (network-oriented, persistent)
-
IndexedDB (structured, large-scale persistence)
Choose the one that matches your data lifetime and security model, not just convenience.





