Skip to content

Local-First Software ​

What Is Local-First Software? ​

Local-first software keeps the primary copy of your data on your own device. The app reads and writes locally, and a sync engine exchanges changes with servers and other devices in the background. The cloud is still useful for backup, multi-device access and collaboration, but the app no longer depends on it to work.

This is the opposite of a typical web app, where the server holds the data and the browser only shows a view of it. In a local-first app, every interaction is answered from local data, so the app is fast, works offline, and keeps working even if the server is slow or gone.

The term was coined in the 2019 essay Local-first software: You own your data, in spite of the cloud by Martin Kleppmann, Adam Wiggins, Peter van Hardenberg and Mark McGranaghan at Ink & Switch. Since then, local-first has grown into a movement with its own community, conferences and tool directory.

The Seven Ideals of Local-First Software ​

The Ink & Switch essay defines seven ideals that local-first software should aim for:

  1. No spinners: your work at your fingertips. Reads and writes are local, so the app responds instantly.
  2. Your work is not trapped on one device. Data syncs across all devices of a user.
  3. The network is optional. Everything works offline; sync happens when a connection is available.
  4. Seamless collaboration with your colleagues. Multiple people can work on the same data, ideally in real time.
  5. The Long Now. Data stays accessible for years, even if the vendor's servers disappear.
  6. Security and privacy by default. Data on servers can be encrypted end to end.
  7. You retain ultimate ownership and control. Users can copy, modify and delete their data without asking a service for permission.

Few apps fulfil all seven ideals. In practice, "local-first" is used for a spectrum of architectures. Most production apps start with the first three (speed, multi-device and offline) and keep a server as the source of truth.

How Local-First Apps Work ​

A local-first app has three building blocks:

  1. A local database holds the data the user works with. In the browser it is persisted in IndexedDB, OPFS or localStorage, on mobile usually in SQLite.
  2. Reactive queries connect the database to the UI. When data changes, whether by a local write or by sync, every affected view updates automatically.
  3. A sync engine records local changes, pushes them to the server, pulls remote changes and resolves conflicts.

Because the UI only ever talks to the local database, optimistic UI and offline support come for free: a write is visible immediately and is synced later.

Approaches to Local-First Sync ​

The sync engine is the hardest part of a local-first app. There are three common approaches:

ApproachHow it worksTypical toolsGood for
Server-authoritative syncClients queue local changes and replay them on top of the latest server state; the server decidesSignalDB, RxDB, WatermelonDB, Replicache/ZeroApps with an existing backend, business rules and permissions on the server
Database replicationA client database replicates a subset of a server database (e.g. Postgres or CouchDB)PouchDB with CouchDB, ElectricSQL, PowerSyncApps whose backend is built around that database
CRDTsData types that merge concurrent changes automatically, without a central authorityAutomerge, Yjs, TinyBase (MergeableStore)Collaborative editing, peer-to-peer sync

Server-authoritative sync is the most common choice for business applications: it keeps validation, permissions and conflict rules on the server and works with ordinary REST or GraphQL APIs. CRDTs come closest to the "ownership" and "Long Now" ideals, but make server-side validation and schema changes harder.

Local-First vs. Offline-First vs. Cloud Apps ​

Cloud app (thin client)Offline-first appLocal-first app
Primary copy of the dataServerServer, cached on the clientClient device
Works offlineNoYesYes
Speed of interactionsNetwork round tripInstant for cached dataInstant
Multi-device and collaborationYes (via server)Yes (via server)Yes (via sync)
Data ownershipVendorVendorUser (to varying degrees)

Offline-first describes the capability to work without a network. Local-first is the broader philosophy that the local copy is the primary one. Technically, both rely on the same foundation: a local database with sync.

Benefits and Trade-offs ​

Benefits

  • Instant UI: no loading spinners for reads or writes.
  • Offline support: the app works on trains, planes and bad Wi-Fi.
  • Less backend load: queries run on the client; the server mainly stores and distributes changes.
  • Simpler frontend state: the local database replaces ad-hoc caches and global stores.

Trade-offs

  • Data has to fit on the device. You usually sync a user-specific subset, not the whole database.
  • Sync and conflicts are real work. You need a clear conflict strategy and must handle rejected changes.
  • Permissions move to the sync layer. The server must check every pushed change, because clients can no longer be trusted to only call allowed endpoints.
  • Schema changes are harder. Old clients with offline data must keep working after a migration.
  • Initial load: the first sync of a large dataset can take a while.

Local-First Tools for JavaScript ​

The JavaScript ecosystem offers tools for every layer:

  • Local databases with sync: SignalDB, RxDB, Dexie.js, PouchDB, WatermelonDB, TinyBase, TanStack DB. See the JavaScript database comparison for a detailed feature matrix.
  • Sync engines for existing databases: ElectricSQL and PowerSync (Postgres and other databases), Replicache/Zero.
  • CRDT libraries: Automerge, Yjs.
  • Hosted local-first platforms: InstantDB, Jazz, Triplit and others.

The local-first directory on lofi.so lists many more projects.

Building a Local-First App with SignalDB ​

SignalDB is a reactive local-first database for JavaScript that uses server-authoritative sync with any backend. A local-first setup takes three steps.

1. Create a persisted, reactive collection using the signal library of your framework (here Solid; adapters exist for Angular, Vue, Preact, Svelte, MobX and more):

js
import { Collection } from '@signaldb/core'
import createIndexedDBAdapter from '@signaldb/indexeddb'
import solidReactivityAdapter from '@signaldb/solid'

export const notes = new Collection({
  reactivity: solidReactivityAdapter,
  persistence: createIndexedDBAdapter('notes'),
})

2. Query and write locally. Queries inside effects re-run when data changes; writes are visible immediately:

js
import { createEffect } from 'solid-js'

createEffect(() => {
  const myNotes = notes.find({ archived: false }, { sort: { updatedAt: -1 } }).fetch()
  render(myNotes)
})

notes.insert({ text: 'Local-first!', archived: false, updatedAt: Date.now() })

3. Add sync with your backend. The SyncManager persists the queue of local changes, pushes them to your API, pulls remote changes and resolves conflicts by replaying local changes on the latest server data:

js
import { SyncManager } from '@signaldb/sync'

const syncManager = new SyncManager({
  persistenceAdapter: name => createIndexedDBAdapter(name),
  pull: async ({ apiPath }) => ({ items: await fetch(apiPath).then(res => res.json()) }),
  push: async ({ apiPath }, { changes }) => {
    await fetch(apiPath, { method: 'POST', body: JSON.stringify(changes) })
  },
  registerRemoteChange: (options, onChange) => {
    // e.g. call onChange() when your WebSocket or SSE connection reports new data
  },
})

syncManager.addCollection(notes, { name: 'notes', apiPath: '/api/notes' })

This covers the first four local-first ideals (instant UI, multi-device, offline, collaboration through the server) while your backend stays the source of truth. There are complete examples for a plain HTTP API, Supabase, Firebase and Appwrite.

Frequently Asked Questions ​

What does local-first mean? ​

Local-first means that an application stores the primary copy of its data on the user's device, reads and writes it locally, and synchronizes with servers and other devices in the background.

What is a local-first database? ​

A database that runs inside the client application (browser, desktop or mobile app), persists data on the device and has a sync mechanism to exchange changes with a server or other clients.

Is local-first the same as offline-first? ​

Not exactly. Offline-first means the app keeps working without a network. Local-first additionally treats the local copy as the primary one and aims for user ownership of data. Both are built on a local database with sync.

Do local-first apps still need a server? ​

Usually yes: for sync between devices, backups, collaboration, authentication and permissions. The difference is that the app does not need the server to work.

Do I need CRDTs to build a local-first app? ​

No. CRDTs are one way to merge concurrent changes, well suited for collaborative editing. Many local-first apps use server-authoritative sync instead, where local changes are replayed on the latest server state. SignalDB uses this approach.

Conclusion ​

Local-first software moves the primary copy of data to the user's device and treats the network as optional. The result is faster, more resilient apps, at the cost of handling sync, conflicts and permissions carefully. For JavaScript developers, the main decision is the local database and its sync model. Compare the options, or get started with SignalDB to add local-first data to your existing app and backend.

Released under the MIT License.