Local-First Sync and Collaboration
Why this chapter matters
Offline-first keeps an app usable during network failure. Local-first goes further: the user's device becomes a primary place where work can be created, edited, searched, and trusted before the server catches up. Collaborative local-first systems also need conflict policy, merge semantics, presence, history, permissions, and recoverability.
This is not only a data-layer topic. It changes UX copy, storage choices, authorization, incident response, analytics, and support workflows.
Core model
Local-first architecture has five contracts:
| Contract | Question |
|---|---|
| Local truth | What can the user do before the server confirms it? |
| Sync truth | How do changes move between device, server, and peers? |
| Conflict truth | What happens when valid changes disagree? |
| Permission truth | What happens when access changes while a device is offline? |
| Recovery truth | How does the user understand and repair broken sync? |
Architecture topology
UI state
-> local command model
-> durable local store
-> sync queue
-> conflict/merge engine
-> remote API or sync service
-> acknowledgements and patches
-> user-visible sync state
Do not let components write directly to an ad hoc local cache. The app needs a command model that can be replayed, inspected, retried, rejected, or merged.
Storage decisions
| Need | Candidate | Architecture concern |
|---|---|---|
| Structured local records | IndexedDB | Schema migrations, quota, transaction boundaries, debugging |
| Small preferences | localStorage | Synchronous API, sensitive-data risk, limited durability guarantees |
| Large binary artifacts | Cache Storage or Origin Private File System | Quota, eviction, backup, user data expectations |
| Multi-tab coordination | Web Locks, BroadcastChannel, SharedWorker | Leader election, duplicate sync, tab lifecycle |
| Offline shell | Service worker | Cache invalidation, update lifecycle, fallback correctness |
Sensitive data should not be stored locally unless the product has a clear threat model, retention policy, and user-facing reason.
Conflict strategies
| Strategy | Best for | Risk |
|---|---|---|
| Last write wins | Low-value preferences | Silent data loss |
| Server wins | Compliance-sensitive records | User confusion after local work disappears |
| Client wins | Personal drafts | Overwriting authoritative data |
| Field-level merge | Profile-like records | False confidence when fields are semantically linked |
| Operation-based merge | Collaborative editing and counters | More complex command design |
| CRDT/OT | Real-time collaboration | Library and data-model commitment |
| Manual resolution | High-value ambiguous conflicts | UX and support burden |
The conflict policy must be chosen per domain object, not per app.
Permission and identity drift
The hard case is not network failure. The hard case is a valid local action that becomes invalid before sync:
- user role changed
- account disabled
- record deleted remotely
- tenant membership removed
- policy changed
- remote version violates a local assumption
The sync engine must distinguish:
- retryable failure
- permanent validation failure
- authorization failure
- conflict requiring merge
- destructive remote change requiring user review
User experience states
Every local-first surface needs visible state language:
| State | UX obligation |
|---|---|
| Saved locally | User can close the tab/device without losing work locally. |
| Syncing | User knows the app is trying to reach the server. |
| Synced | User knows the change is durable remotely. |
| Needs attention | User can repair conflict or rejected change. |
| Offline | User knows what still works and what is unavailable. |
| Out of date | User knows remote changes may be missing. |
Avoid vague success messages when the server has not acknowledged the change.
Production controls
- durable sync queue with schema version
- command id and idempotency key
- monotonic local sequence number
- remote acknowledgement tracking
- sync pause/resume controls
- backoff and retry budget
- conflict fixture library
- local database migration tests
- storage quota monitoring
- support export for sync diagnostics with sensitive fields redacted
Observability
Track:
- unsynced command count
- oldest unsynced command age
- sync success/failure rate
- conflict rate by object type
- manual resolution rate
- local database migration failure
- storage quota errors
- multi-tab duplicate sync attempts
- authorization rejects during replay
- support tickets with sync diagnostics attached
Review checklist
- Domain objects have explicit conflict policy.
- The local store has migration, quota, and corruption handling.
- Multi-tab sync cannot duplicate side effects.
- Offline-created commands have idempotency keys.
- Permission drift has user-visible recovery.
- Sensitive local data is minimized and justified.
- Sync state is visible, specific, and accessible.
- Observability can identify stuck users before support reports.
Exercises
- Choose a collaborative object and define its conflict policy: last-write, field merge, operation merge, CRDT, or manual resolution.
- Design a sync queue schema with command id, local sequence, remote acknowledgement, retry count, and failure class.
- Write UX copy for saved locally, syncing, synced, conflict, authorization rejected, and storage full states.
- Create an incident drill for a release that corrupts the local database migration for 2% of users.
Source lens
Use local distributed-systems material for consistency, replication, conflict, and failure thinking. Use browser platform references for IndexedDB, storage quota, service workers, BroadcastChannel, and Web Locks. Use frontend security sources for local sensitive-data risk.