โ† Back to Blog
ยท5 min read

Local-First Web Apps with Dexie and DexieCloud

#web#local-first#tutorial

Your app should work without the internet. Here's how to build local-first web apps using IndexedDB, Dexie.js, and DexieCloud for seamless sync.

Local-First Web Apps with Dexie and DexieCloud

๐Ÿš€ Quick punch: Build apps that keep working when the internet ghosts you. Local-first means the device is the source of truth โ€” the cloud is just the nice-to-have sync smoothie.

TL;DR

  • Use the browser's IndexedDB for offline-first storage.
  • Dexie.js gives you a delightful, promise-based API over IndexedDB.
  • DexieCloud adds low-friction two-way sync so your users' data follows them โ€” without you hosting a backend.
  • Start local-first, add sync later. Instant UI, offline resilience, better data ownership.

Why we should care (short and spicy)

If your app "spins and dies" when the network hiccups, users get annoyed, uninstall, and never come back. Local-first flips the script: instant reads/writes, seamless offline use, and background sync that quietly keeps devices in sync. It's like building a native app, but with web tech โ€” and users actually own their data.

Pro tip: ship the simplest local UX first. Sync is an add-on, not a requirement.


What does "local-first" actually mean?

Short answer: the device is the source of truth. Your UI talks to a local DB. Writes are instant. Sync happens in the background. The cloud is an eventually-consistent replica, not the gatekeeper.

Benefits:

  • Instant UI feedback โ€” no network wait.
  • Works offline reliably.
  • Users retain control of their data.
  • Easier to reason about UX (no loading spinners everywhere).

Trade-offs exist (storage limits, conflict resolution), but we'll cover those.


IndexedDB: the browser's built-in DB (and why it's awkward)

IndexedDB is powerful โ€” transactional, supports indexes, stores blobs, and can hold lots of data. It's also famously... verbose.

Example (raw IndexedDB, because you deserve to cringe once in your life):

const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const store = db.createObjectStore("tasks", { keyPath: "id" });
  store.createIndex("status", "status");
};
request.onsuccess = (event) => {
  const db = event.target.result;
  const tx = db.transaction("tasks", "readwrite");
  tx.objectStore("tasks").add({ id: 1, title: "Buy milk", status: "todo" });
};

Yes. That. Dexie saves you from writing this nonsense every time.


Dexie.js: IndexedDB for humans

Dexie wraps IndexedDB in a friendly, promise-based API. Schema migrations, queries, transactions โ€” all nicer. If you know ORMs, Dexie will feel familiar. If you don't, it's still delightful.

Minimal Dexie setup:

import Dexie from "dexie";

const db = new Dexie("TaskManager");
db.version(1).stores({
  tasks: "++id, status, project, dueDate",
  projects: "++id, name",
});

// Add a task
await db.tasks.add({ title: "Buy milk", status: "todo", date: new Date() });

// Query
const todos = await db.tasks.where("status").equals("todo").toArray();

Nice. No callback hell. No transaction voodoo.

Callout โ€” Dexie is great because:

  • It normalizes the weirdness of IndexedDB.
  • It supports versioning and migrations.
  • It gives you fluent queries and async/await ergonomics.

Building a local-first app (pattern)

Design pattern checklist:

  1. Make IndexedDB (Dexie) your primary data source.
  2. Have your UI read/write directly to Dexie.
  3. Reflect local changes in the UI immediately.
  4. Sync to the cloud in the background (DexieCloud or custom sync).
  5. Handle conflicts thoughtfully (merge strategies, last-write-wins, or CRDTs if needed).

Architecture sketch:

User Action โ†’ Local IndexedDB (Dexie) โ€” UI updates immediately โ†’ Background Sync (DexieCloud) โ†’ Cloud DB โ†’ Other devices pull changes


DexieCloud: Sync without building a backend

DexieCloud plugs into your Dexie DB and handles two-way sync, auth, access control, and conflict resolution. For many apps it's "good enough" out of the box:

  • Two-way sync: local โ†” cloud
  • Built-in auth: simple email/OTP flows
  • Access control: share data securely
  • Conflict resolution: property-level merges and sensible defaults
  • Service worker support for background sync on supported browsers

Setup sketch:

import Dexie from "dexie";
import dexieCloud from "dexie-cloud-addon";

const db = new Dexie("TaskManager", { addons: [dexieCloud] });
db.version(1).stores({
  tasks: "++id, status, project, dueDate",
  projects: "++id, name",
});

db.cloud.configure({
  databaseUrl: "https://your-db-id.dexie.cloud",
  requireAuth: true,
});

One line summary: your Dexie code keeps working. DexieCloud sneaks in and makes it sync.


Trade-offs (read this before you commit to local-first)

  • Storage limits: Chrome is generous; Safari can be pruney. Consider prompting users to install your PWA to get better storage guarantees.
  • Conflict resolution: DexieCloud does last-write-wins at property level by default. For complex domains, design merge logic or use CRDTs.
  • Initial sync size: syncing millions of records to a client is unrealistic. Keep per-user datasets reasonable.
  • Pricing & hosting: DexieCloud has free tiers and pricing; self-hosting is an option if you need full control.

UX advice (tiny but powerful)

  • Show instant changes. Let sync be invisible.
  • Surface sync status unobtrusively (e.g., a tiny cloud icon with subtle state).
  • For destructive actions, consider local undo that syncs cancellation.
  • If conflicts happen, prefer automatic merges and only show UI when manual resolution is required.

When local-first is a great fit

  • Personal tools: notes, journals, habit trackers
  • Productivity apps: task managers, timers, editors
  • Field work: apps used offline in the wild (surveys, inspections)
  • Privacy-first apps: users own their data

Not ideal when the server must be the sole authority (banking ledgers, some legal workflows) or when real-time collaborative CRDT-level sync is the requirement.


TL;DR โ€” The Funky Summary

  • Build your app so it works with no network. The device is the source of truth.
  • Use Dexie to make IndexedDB pleasant to work with.
  • Add DexieCloud when you want simple, reliable sync without managing servers.
  • Start local, add sync later. Your users will forgive you for everything except slow, brittle apps.

Further reading

  • Dexie.js documentation โ€” getting started guide with framework-specific examples
  • DexieCloud โ€” the sync service; pricing, docs, and self-hosting options
  • Local-first Software โ€” the original Ink & Switch essay that defined the term and the principles; still the best articulation of why this matters
โ†‘ back to top