IndexedDB in Practice: Large-File Storage and Offline Architecture in the Browser
Why IndexedDB?
Browser storage options each have clear limits:
| Option | Capacity | Data Types | Sync API | Use Case |
|---|---|---|---|---|
| Cookie | ~4KB | String | ✅ | Session identifiers |
| localStorage | ~5MB | String | ✅ | Simple settings |
| sessionStorage | ~5MB | String | ✅ | Temporary state |
| IndexedDB | Hundreds of MB–GB | Any structured data | ❌ Async | Large files, offline apps |
For tool sites that need to cache PDF processing intermediates, WASM modules, or user upload history in the browser, IndexedDB is the only viable choice.
Core Concepts
Database → Object Store → Record
Database: "toolsku-cache"
├── ObjectStore: "wasm-modules" (key: moduleName)
├── ObjectStore: "file-cache" (key: fileHash, index: timestamp)
└── ObjectStore: "user-preferences" (key: settingKey)
Transactions and Concurrency
IndexedDB transactions auto-commit, and only one read/write transaction per ObjectStore is allowed at a time:
const tx = db.transaction(['file-cache'], 'readwrite');
const store = tx.objectStore('file-cache');
await store.put({ hash: 'abc123', data: arrayBuffer, timestamp: Date.now() });
await tx.done; // Wait for transaction to complete
IndexedDB in ToolsKu
WASM Module Caching
ffmpeg.wasm is about 30MB and takes 3–5 seconds to load each time. After caching in IndexedDB, subsequent visits are nearly instant:
async function getWasmModule(name: string): Promise<ArrayBuffer> {
const cached = await idb.get('wasm-modules', name);
if (cached) return cached;
const response = await fetch(`/wasm/${name}.wasm`);
const buffer = await response.arrayBuffer();
await idb.put('wasm-modules', buffer, name);
return buffer;
}
Chunked Storage for Large Files
Files over 50MB are not ideal to store in IndexedDB all at once. A chunking strategy:
File (200MB) → split into 4MB chunks → store each in IndexedDB
key: "file:{hash}:chunk:{index}"
Read in order and concatenate → restore full file
Version Migration
When the database schema changes, handle migration in onupgradeneeded:
const request = indexedDB.open('toolsku-cache', 2); // version 2
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (event.oldVersion < 2) {
db.createObjectStore('processing-history', { keyPath: 'id' });
}
};
Best practice: add ObjectStores only—never remove them. Bump the version number to trigger migration and avoid deleting user data.
IndexedDB vs OPFS
| Feature | IndexedDB | OPFS (Origin Private File System) |
|---|---|---|
| API maturity | Broad support | Chrome 86+ / Safari 15.2+ |
| Read/write model | Key-value store | True file system |
| Performance | Moderate | Higher (especially sync access) |
| Best for | Structured cache | Large file I/O |
ToolsKu primarily uses IndexedDB, with OPFS as a performance optimization on Chrome.
Summary
IndexedDB is essential infrastructure for offline-first browser tools. Well-designed ObjectStores, WASM caching, and chunked storage strategies let ToolsKu stay responsive even on slow networks.
Try these browser-local tools — no sign-up required →