# Why IndexedDB is the right working copy for a local-first app Source: https://selfstore.dev/blog/indexeddb-as-working-copy A local database as the app's live state - not a server round-trip, not localStorage - and the separation between a working copy and a durable home that makes it safe. Every app has a working copy: the live state the interface reads and writes as the user does things. The only real question is where it lives. The reflex answer is "on a server, fetched over the network". A local-first app answers differently: the working copy lives on the device, in a local database, and the network is for backup and sync, not for reading your own data back. For browser apps that database is IndexedDB. This article is about why it is the right home for the working copy, and about the one architectural distinction that keeps that choice from being reckless. ## Working copy versus durable home The mistake is to treat the on-device store as the durable copy. IndexedDB can be cleared: the user hits "clear site data", switches browsers, or loses the laptop, and it is gone. Treat it as your source of truth and you have built a data-loss machine. [selfstore](https://selfstore.dev/) separates the two roles deliberately. The **working copy** lives in IndexedDB and absorbs every keystroke at local-database speed. The **durable home** is storage the user already owns - a file on disk, their Google Drive, a WebDAV server, an S3 bucket - where the app writes an encrypted backup as state changes. The working copy is fast and disposable; the home is durable and owned. Lose the working copy and you reattach the home and it is back. Once that split is in place, IndexedDB is exactly the right tool for the fast, disposable half, and its clearability stops being a liability. ## Why a local database and not a server round-trip Reading your own data over the network buys you latency, an offline failure mode, and a server to run. A local working copy removes all three: - **It is synchronous with the user's intent.** Writes land in milliseconds, with no spinner between a keystroke and the state that reflects it. - **It works offline by construction.** The data is already here. A plane, a tunnel, a dead connection change nothing, because there was no round-trip to fail. - **It has no hosting cost and no uptime.** The app ships as static files; the data lives on the device. There is no server whose bill or outage you own. The browser is a serious runtime for this. IndexedDB stores gigabytes of structured data, indexed and queryable, asynchronously so it never blocks the interface. It is the only browser storage designed for real application data. ## Why not localStorage localStorage is the tempting shortcut, and it is a trap for anything past a toy. It is synchronous, so every read and write blocks the main thread. It stores strings only, so structured data means serializing and parsing on every access. And it is capped at a few megabytes. It is fine for a theme preference and wrong for an app's working copy. IndexedDB exists precisely because application state needs asynchronous access, structured records, and room to grow. ## The reactive loop A working copy is only useful if the interface tracks it. selfstore exposes changes through an `onChange` subscription: the same signal fires for the user's own writes and for state folded in from other devices, so the render path is identical whether an edit came from this keyboard or a sync from a phone. The [quick start](https://selfstore.dev/docs/quick-start) shows the whole loop - open a store, put a record, subscribe to render - in a handful of lines. The interface reads local state and re-renders on change; nothing in that loop touches a network. ## Surviving "clear site data" Because the working copy is disposable by design, the failure that ends naive IndexedDB apps is a non-event here. Cleared cache, new browser, wiped device: reattach the durable home and the working copy is rebuilt from the last encrypted backup. The discipline the browser does **not** hand you - the one that turns raw IndexedDB into trustworthy persistence - is exactly this loop: a backup format, encryption done at the device edge, and the habit of writing to the owned home on change so the disposable copy is always reconstructable. That is the library-shaped gap selfstore fills; the [durable homes it can write to](https://selfstore.dev/) are storage the user already has. ## The honest limits A local working copy is scoped to what fits and belongs on one device. It is not a shared multi-user database: cross-user features still need a server for the shared state, by definition. And whole-state backup and sync are proportional to the size of the state, which is ideal at the scale of one person's app data and wrong for multi-gigabyte datasets. Within those limits - one person's data, at human scale - a local database as the working copy is not a compromise you tolerate for offline support. It is faster, cheaper and more private than the server round-trip it replaces, and [multi-device sync](https://selfstore.dev/blog/sync-without-server) runs on top of the same owned storage without adding a backend of yours. Map of this site for a model: https://selfstore.dev/llms.txt Every page in one file: https://selfstore.dev/llms-full.txt