Local-First Architecture
A local-first app has one job: make local user data the trustworthy source of truth, then coordinate optional backup or sync around it.
flowchart LR
subgraph device["User device"]
ui["UI"]
localStore[(Local store)]
queue[(Write queue)]
exports["Export and backup"]
secureStore["Secure key store"]
end
subgraph remote["Optional remote"]
syncApi["Sync API"]
backupBucket[(Backup store)]
serverDb[(Server database)]
end
ui --> localStore
ui --> queue
queue --> localStore
localStore --> exports
secureStore --> exports
queue --> syncApi
syncApi --> serverDb
exports --> backupBucket
Layers
| Layer | Responsibility | Quality gate |
|---|---|---|
| UI | Shows current local state, pending writes, offline mode, and failures | users can tell what is saved, pending, or failed |
| Local store | Owns source-of-truth data on the device | migrations preserve real data |
| Derived indexes | FTS, vector indexes, caches, thumbnails, search tables | can be rebuilt from source data |
| Write queue | Persists local actions before network sync | retries are idempotent and visible |
| Backup/export | Lets the user recover without trusting your server | restore drill passes on a clean install |
| Sync engine | Coordinates remote state only when needed | conflict semantics are product-defined |
| Key store | Protects encryption keys and secrets | key loss and restore paths are documented |
Source, Cache, Index, Artifact
Label every data set before picking storage:
| Data type | Example | Rule |
|---|---|---|
| Source of truth | transactions, notes, workouts, user edits | never delete without explicit user action or verified migration |
| Cache | API response cache, image cache | can be evicted and re-fetched |
| Derived index | FTS index, embedding index, denormalized summary | can be rebuilt from source of truth |
| Attachment | receipt image, PDF, audio note | store with manifest and integrity metadata |
| Export artifact | CSV, JSON, SQLite app document, encrypted backup | validate before telling the user it succeeded |
Gotcha: Treating source-of-truth data as cache is the fastest way to build a local-first app that loses user data.
Minimal V1 Shape
For most personal tools and privacy-first apps:
- Store structured app data in SQLite or Room.
- Store large binary attachments as files with DB metadata.
- Export CSV or JSON for user inspection.
- Export a full backup format for restore.
- Add an offline write queue only when there is a remote endpoint.
- Add sync after backup and restore are tested.
When To Add Sync
Add sync when one of these is true:
- Users need the same source-of-truth data on multiple devices.
- Collaboration is part of the product.
- Losing the device would destroy high-value data and backup alone is not enough.
- Server-side processing must update local state later.
Stay local-only or backup-only when:
- The product is privacy-sensitive and single-device by default.
- Exports satisfy user ownership needs.
- Network calls would reduce trust.
- You cannot yet explain conflict resolution to a user.