Cache API vs IndexedDB vs localStorage
Choosing the right storage mechanism
Section titled “Choosing the right storage mechanism”The browser offers three main client-side storage APIs for persistent data. They serve different purposes and complement each other — the key is picking the right one for each use case.
| Storage | Best for | Data type | Async? | Approximate size |
|---|---|---|---|---|
| Cache API | HTTP responses, static assets | Request/Response | Yes | Large (GBs possible) |
| IndexedDB | Structured app data, offline records | JS objects, blobs | Yes | Large (GBs possible) |
| localStorage | Tiny config strings, UI preferences | String only | No (synchronous) | ~5 MB |
Cache API
Section titled “Cache API”The Cache API stores Request/Response pairs. It is shaped like HTTP — the key is a URL (or Request object) and the value is a Response. This makes it ideal for:
- Pre-caching static assets (HTML, CSS, JS, images) for offline use
- Runtime caching of API responses inside a Service Worker
- Serving stale content while a fresh version is fetched in the background
The Cache API is most useful in combination with a Service Worker. See the PWA course for Service Worker caching patterns.
IndexedDB
Section titled “IndexedDB”IndexedDB stores arbitrary JavaScript values (objects, arrays, blobs, typed arrays) in object stores with optional indexes. It is the right choice for:
- Offline app data: user records, messages, documents
- Large blobs: audio, video, images that are part of the app’s data model
- Data that needs to be queried, sorted, or filtered client-side
- Any structured data that does not fit the URL-keyed HTTP model
localStorage
Section titled “localStorage”localStorage stores string key/value pairs synchronously. It blocks the main thread on every read and write. Despite its convenience, it is only suitable for:
- Tiny preferences: theme choice, language, a single token
- Values read on every page load where the synchronous access is acceptable
- Anything under a few kilobytes
Never store large objects or do frequent writes in localStorage — it will stall rendering.
Decision diagram
Section titled “Decision diagram”flowchart TD
Q[What do you need to store?]
Q --> A{HTTP responses
or assets?}
A -->|Yes| CacheAPI[Cache API
caches.open / put / match]
A -->|No| B{Structured
app data?}
B -->|Yes| IDB[IndexedDB
object stores + indexes]
B -->|No| C{Tiny string
preference?}
C -->|Yes| LS[localStorage
setItem / getItem]
C -->|No| IDB Trade-offs
Section titled “Trade-offs”| Choice | Benefit | Cost |
|---|---|---|
| Cache API for HTTP responses | Matches the Request/Response model directly — good fit for pre-cached assets and API responses | Not a good fit for structured data — use IndexedDB instead when you need to query or index it |
| IndexedDB for app data | Query, index, and transaction support for complex data | Harder API to use — you manage schema versions and object stores yourself |
| localStorage for small preferences | Simple to read and write, synchronous syntax | Blocks the main thread on every access — unsuitable for large data or frequent writes |
Common mistakes
Section titled “Common mistakes”- Using the Cache API to store plain JS objects by JSON-stringifying them into a Response yourself — unnecessary complexity; if the data isn’t an HTTP request/response, reach for IndexedDB from the start.
- Using localStorage for large data or an entire cache — localStorage’s synchronous I/O blocks the main thread and stalls the UI; move it to IndexedDB or the Cache API.
- Assuming the Cache API and IndexedDB compete and you must pick one — most PWAs use both together: Cache API for assets, IndexedDB for app data.