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

LayerResponsibilityQuality gate
UIShows current local state, pending writes, offline mode, and failuresusers can tell what is saved, pending, or failed
Local storeOwns source-of-truth data on the devicemigrations preserve real data
Derived indexesFTS, vector indexes, caches, thumbnails, search tablescan be rebuilt from source data
Write queuePersists local actions before network syncretries are idempotent and visible
Backup/exportLets the user recover without trusting your serverrestore drill passes on a clean install
Sync engineCoordinates remote state only when neededconflict semantics are product-defined
Key storeProtects encryption keys and secretskey loss and restore paths are documented

Source, Cache, Index, Artifact

Label every data set before picking storage:

Data typeExampleRule
Source of truthtransactions, notes, workouts, user editsnever delete without explicit user action or verified migration
CacheAPI response cache, image cachecan be evicted and re-fetched
Derived indexFTS index, embedding index, denormalized summarycan be rebuilt from source of truth
Attachmentreceipt image, PDF, audio notestore with manifest and integrity metadata
Export artifactCSV, JSON, SQLite app document, encrypted backupvalidate 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:

  1. Store structured app data in SQLite or Room.
  2. Store large binary attachments as files with DB metadata.
  3. Export CSV or JSON for user inspection.
  4. Export a full backup format for restore.
  5. Add an offline write queue only when there is a remote endpoint.
  6. 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.