How We Handle Offline Sync with IndexedDB
A clipboard sync tool that only works when you're online isn't much of a tool. Connectivity drops on trains, in lifts, on flaky hotel Wi-Fi, and during those inexplicable moments when your broadband decides to take a break. Pastetory keeps working through all of it.
Here's how we built offline-first sync into a web application using IndexedDB, pending queues, and careful conflict resolution.
Why IndexedDB?
Browsers offer several storage options: localStorage, sessionStorage, Cache API, and IndexedDB. For our use case, IndexedDB is the only viable choice:
- localStorage is limited to 5–10MB and only stores strings. Useless for images.
- sessionStorage disappears when the tab closes.
- Cache API is designed for HTTP responses, not structured application data.
- IndexedDB offers hundreds of megabytes of storage, supports binary data (Blobs and ArrayBuffers), and persists across sessions.
IndexedDB's API is notoriously awkward — callback-based, transaction-heavy, and verbose. We wrap it in a thin async layer that exposes simple get, put, and delete operations, hiding the complexity from the rest of the application.
The Local Cache
When you open Pastetory, your snippet history loads from IndexedDB first, then syncs with the server in the background. This means:
- The app renders instantly, even on slow connections
- Your full history is available offline
- New data from other devices merges in seamlessly when it arrives
Each snippet is stored as a structured object in IndexedDB, including its encrypted content, metadata, timestamps, and type information. Images store a thumbnail locally for instant display, with the full-resolution version fetched on demand.
The Pending Upload Queue
When you paste something while offline (or during a network interruption), the snippet is immediately saved to IndexedDB and added to a pending upload queue. The queue is persistent — if you close the tab and come back later, the pending items are still there waiting to sync.
When connectivity returns, the pending queue flushes automatically:
- Each pending snippet is posted to the API in order
- Successful uploads are removed from the queue
- Failed uploads are retried with exponential backoff
- After 3 non-network failures, an item is dropped (the user can re-paste)
For images and files, the pending queue stores a reference to the cached thumbnail rather than the full binary. When the flush runs, it uses the presigned upload path — fetching the full image from IndexedDB cache, uploading directly to object storage, then posting the metadata to our API.
Handling Large Data Without Crashing
Early in development, we hit a critical bug: storing large images directly in the pending queue caused IndexedDB to consume so much memory that the browser tab crashed (Chrome's dreaded "Aw, Snap!" error). The solution was to separate storage concerns:
- Pending queue entries store only a reference key (
pending:<id>) - The actual binary data lives in a separate IndexedDB object store
- On retry, the flush reconstitutes the full payload from the reference
- Legacy oversized entries are detected and auto-deleted using cursor-based iteration
This pattern keeps the pending queue lightweight while still supporting large file uploads offline.
Sync Strategy: Last-Write-Wins with Server Authority
Clipboard sync has a simpler conflict model than collaborative editing. Snippets are immutable once created — you don't edit a paste, you create a new one. This means true conflicts are rare. The main sync challenge is ensuring all devices eventually see the same set of snippets.
Our strategy:
- The server is the source of truth for snippet existence and ordering
- Each device fetches bundles from S3 and merges them into its local IndexedDB
- Duplicate detection (by snippet ID) prevents the same item appearing twice
- Deletions are propagated by removing the snippet from the server bundle; other devices pick this up on next sync
Real-Time Updates via WebSocket
While IndexedDB handles persistence, real-time delivery uses WebSocket connections. When a snippet is created on one device, a notification is pushed to all other connected devices instantly. They then fetch the new data and merge it into their local cache.
If the WebSocket connection drops (which it will — mobile browsers are aggressive about killing background connections), the next time the app is focused it performs a full sync to catch up on anything missed.
The Result
From the user's perspective, Pastetory always works. Paste something offline and it syncs when you're back online. Open the app on a plane and your full history is there. Switch between devices and everything is up to date within a second. The complexity lives entirely behind the scenes — which is exactly where it should be.