LabHub

Blog

CRDT and Local-First Engines in 2026 — A Deep-Dive Comparison of Yjs, Automerge 3, Loro, Replicache, Liveblocks, Zero

한국어English日本語

Prologue — The 7 Ideals of Ink and Switch, Seven Years Later

In April 2019, Ink and Switch published "Local-first software: You own your data, in spite of the cloud." Co-authored by Martin Kleppmann, Peter van Hardenberg, and Mark McGranaghan, the essay called out the paradox of cloud SaaS, where your data is technically yours but practically out of reach. They proposed seven ideals.

  1. No spinners — fast operations, with UI responding instantly on the local device
  2. Your work is not trapped — access across multiple devices
  3. The network is optional — works offline
  4. Seamless collaboration — supports collaboration
  5. The Long Now — works over long timescales
  6. Security and privacy by default — end-to-end encryption
  7. You retain ultimate ownership — you own the data

It was an ambitious vision in 2019. By 2026, there are production stacks that hit four or five of those ideals simultaneously. CRDTs (Conflict-free Replicated Data Types) have matured and offline-first patterns are now standard across web and mobile.

This post compares eight major engines in that ecosystem.

I will spell out what each one can and cannot do, the trade-offs involved, and what is happening in the Korean and Japanese markets.


1. Why Local-First, Why Now?

Six or seven years passed since the original essay. Three forces drove the resurgence.

1. WASM and TypeScript matured. Loro, Automerge, and Yjs all ship WASM or pure JS builds. Five years ago, running a CRDT in the browser was an experiment. Today it fits inside a 1 MB WASM blob.

2. SQLite revival. Cloudflare D1, Turso, libSQL, and SQLite-on-WASM (the @sqlite.org/sqlite-wasm package) all took off simultaneously. The premise that every client carries a local SQLite became a realistic baseline again.

3. The rise of AI copilots. Tools like Cursor, Linear, and Notion AI made "must work offline, must be fast, must collaborate" the default SaaS expectation. A 200 ms RTT to the server is no longer acceptable.

4. Mobile-first markets. In Indonesia, India, Vietnam and similar regions with unstable networks, offline operation became a non-negotiable requirement. PowerSync grew out of those needs.

Of the seven ideals, two are still genuinely hard in 2026.


2. CRDT Fundamentals — State-Based vs Operation-Based

CRDTs split into two families.

State-Based CRDTs (CvRDT — Convergent)

You merge entire states. The merge function must be commutative, associative, and idempotent. Any order and any number of merges must produce the same result.

Representative types:

Pros: simple, converges as long as any message arrives at least once. Cons: payload is large because you must send the entire state.

Operation-Based CRDTs (CmRDT — Commutative)

You broadcast each change as an operation. The operations themselves must commute. Messages need exactly-once delivery.

Representative types:

Pros: small payloads, only deltas travel on the wire. Cons: more demanding infrastructure, because you have to handle loss and duplication.

Family Tree of Real Engines

EngineAlgorithm familyTextTree
YjsYATA (op-based RGA variant)StrongDecent (nested Y.Map)
AutomergeRGA-like + hash-linked opsGoodGood (JSON tree)
LoroFugue + Movable TreeStrongStrong (movable)
Replicache/ZeroLWW + per-row reorderPlain text onlyRow-based
LiveblocksBuilt on top of YjsYjsYjs
TinybaseLWW-RegisterWeakWeak

The key line: CRDTs branch by data model. If you want a collaborative text editor, Yjs, Automerge, and Loro are the candidates. For JSON-tree sync, Automerge or Loro. If simple LWW is enough, Replicache or Tinybase keep things lightweight.


3. Yjs — The Engine Behind the De-Facto Standard

Yjs began in 2017 from Kevin Jahns's PhD work. By 2026, it has become the de-facto standard inside collaborative editors.

Why Yjs Won

  1. YATA algorithm — Yet Another Transformation Approach, an RGA variant that performs well in both memory and CPU.
  2. A pool of shared typesY.Doc, Y.Text, Y.Array, Y.Map, Y.XmlFragment. Every shared type that a collaborative editor needs is already there.
  3. A pool of editor adaptersy-prosemirror, y-tiptap, y-codemirror.next, y-quill, y-lexical, y-blocknote. When a new editor appears, an adapter usually follows quickly.
  4. Transport-agnostic — WebSocket, WebRTC, libp2p, IndexedDB. You pick the sync channel.

A TipTap + Yjs Slice

The most common pattern. Two TipTap instances synchronize in real time.

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'

const ydoc = new Y.Doc()
const provider = new WebsocketProvider('wss://demos.yjs.dev', 'my-room', ydoc)

const editor = new Editor({
  extensions: [
    StarterKit.configure({ history: false }), // Yjs ships its own undo
    Collaboration.configure({ document: ydoc }),
    CollaborationCursor.configure({
      provider,
      user: { name: 'Alice', color: '#f783ac' },
    }),
  ],
  element: document.querySelector('#editor')!,
})

Thirty lines and you have a working collaborative editor. Open two browsers in the same room and they sync immediately.

Yjs Limitations

Those two limitations are exactly why Loro showed up.


4. Automerge 3 — 10x Compression Through Column-Oriented Storage

Automerge is led by Martin Kleppmann himself. It started at the same time as Yjs but aimed at a more general target: a general-purpose CRDT that merges entire JSON trees. More general meant heavier.

The Automerge 3 Shake-Up

Through Automerge 2, document size and memory usage were the biggest weak points. Automerge 3, released in autumn 2025, flipped the table.

Automerge 3 Skeleton

import * as A from '@automerge/automerge'

// Create a doc
let doc1 = A.from({ tasks: [] as Array<{ title: string; done: boolean }> })

// Mutate inside a transaction — looks like plain JS
doc1 = A.change(doc1, (d) => {
  d.tasks.push({ title: 'Read papers', done: false })
  d.tasks.push({ title: 'Write CRDT post', done: false })
})

// A replica somewhere else
let doc2 = A.clone(doc1)
doc2 = A.change(doc2, (d) => {
  d.tasks[0].done = true
})

doc1 = A.change(doc1, (d) => {
  d.tasks.push({ title: 'Code review', done: false })
})

// Merge — from either side
const merged = A.merge(doc1, doc2)
console.log(merged.tasks) // three tasks, the first is done: true

Automerge Repo

The more important Automerge 3 change is @automerge/automerge-repo. It is not just a library — it is a document store plus a sync protocol.

Yjs vs Automerge — An Honest Comparison

AspectYjsAutomerge 3
Editor integrationsRichSparse, you write your own
Data modelFixed shared typesArbitrary JSON
Document sizeSmallSmall as of v3
Merge performanceVery fastFast
TypeScript typingWeakStrong, generic-friendly
Tree movesWeakDecent
Learning curveSteepModerate

For collaborative editors, pick Yjs. For general JSON-document sync, pick Automerge.


5. Loro 1.0 — The Rust-Based Standard Candidate

Loro is a Rust-based CRDT library that emerged in late 2023. With version 1.0 released in 2025 it has become a serious candidate for the next standard. The team is led by Zixuan Chen.

Why Loro Matters

  1. Movable Tree as a first-class primitive — moving a tree node to a different parent is handled in a concurrency-safe way. This is the hardest problem inside block editors like Notion or Linear.
  2. Fugue text algorithm — an RGA variant that preserves user intent better in tricky cases like split-line edits.
  3. Rust to WASM — the core is Rust, distributed everywhere via WASM, NAPI, and UniFFI: JS, Swift, Kotlin, Python, Rust.
  4. Rich text as a first-class citizen — unlike Y.Text, which leans on Y.XmlFragment, Loro targeted rich text from day one.
  5. Time travel — rewind and replay the document at any point in history.

Loro Skeleton

import { LoroDoc, LoroList, LoroMap, LoroText } from 'loro-crdt'

const doc = new LoroDoc()
doc.setPeerId(BigInt('0x1234567890abcdef'))

const tasks: LoroList = doc.getList('tasks')
const task1: LoroMap = tasks.insertContainer(0, new LoroMap())
task1.set('title', 'Read papers')
task1.set('done', false)

const note: LoroText = task1.setContainer('note', new LoroText())
note.insert(0, 'In progress...')

// Serialize and deserialize
const snapshot = doc.exportSnapshot()
const docCopy = new LoroDoc()
docCopy.import(snapshot)

Loro Movable Tree

const tree = doc.getTree('blocks')
const root = tree.createNode()
const block1 = tree.createNode(root.id)
const block2 = tree.createNode(root.id)

// Move block2 under block1 — concurrency-safe
tree.move(block2.id, block1.id)

If you implement this directly on top of Yjs, concurrent moves can produce cycles or orphaned nodes. Loro guarantees it at the algorithm level.

Loro Limitations

Still, if you start a new collaborative tool in 2026, it is reasonable to evaluate Loro first. The algorithm is more correct, the data model is richer, and the trajectory is forward-looking.


6. Replicache → Reflect → Zero — Rocicorp's Evolution

Aaron Boodman built Gmail Offline and Google Gears. The libraries he then built at Rocicorp aimed not at collaborative editors but at the sync engine behind B2B SaaS — the backend for tools like Linear, Notion, and Figma.

The evolution.

  1. Replicache (2020 on) — the client holds a SQLite-like local store, sends mutations to the server, and pulls server state. Not a CRDT — LWW with explicit conflict resolution.
  2. Reflect (2022 on) — Replicache plus Cloudflare Workers and Durable Objects to layer real-time multiplayer.
  3. Zero (late 2024 on) — a completely new architecture. ZQL is a client-side query language, Postgres is first class, and IVM (incremental view maintenance) drives reactivity.

The Replicache Idea

import { Replicache } from 'replicache'

const rep = new Replicache({
  name: 'user-123',
  licenseKey: 'YOUR_LICENSE_KEY',
  pushURL: '/api/replicache/push',
  pullURL: '/api/replicache/pull',
  mutators: {
    async createTask(tx, args: { id: string; title: string }) {
      await tx.set(`task/${args.id}`, { title: args.title, done: false })
    },
    async toggleTask(tx, id: string) {
      const task = await tx.get(`task/${id}`)
      if (task) await tx.set(`task/${id}`, { ...task, done: !task.done })
    },
  },
})

// Usage
await rep.mutate.createTask({ id: 'a1', title: 'Write the Replicache post' })

The mutators map runs on both client and server. Once the server has authoritative state, it rebases the client's optimistic mutations.

What is New in Zero

Zero has been in beta since late 2024 and is approaching 1.0 as of May 2026.

import { Zero } from '@rocicorp/zero'

const z = new Zero({
  userID: 'user-123',
  server: 'wss://my.zero.dev',
  schema,
})

// Reactive query
const tasks = z.query.task
  .where('userId', '=', 'user-123')
  .where('done', '=', false)
  .orderBy('createdAt', 'desc')

const view = tasks.materialize()
view.addListener((data) => {
  console.log('Updated task list:', data)
})

Trade-Offs of the Replicache Family

Linear is the most famous user of this pattern. It is the right candidate when you run a large B2B SaaS, do not want to give up server-side authority on permissions and invariants, but still want an instantly responsive UI.


7. Liveblocks vs Partykit vs y-redis — Managed Collaboration Backends

A CRDT library is half the picture. The other half is where documents live and who routes sync messages between peers.

Liveblocks

A French startup. Boldstart-led Series A in 2024. The leading managed backend for collaboration.

Pricing is MAU based. Free up to 10k MAU, paid above.

Partykit

Started by Sunil Pai (formerly Cloudflare and the React core team). Acquired by Cloudflare in 2024.

y-redis and y-mongodb-provider

The self-host camp. Persist Yjs documents in Redis or MongoDB.

Decision Matrix

ScenarioCandidate
Notion clone, including commentsLiveblocks
Small side project, Cloudflare-firstPartykit
Enterprise self-hosty-websocket + y-redis
Global edge, price-sensitivePartykit
Slack-style messaging plus collaborative docsLiveblocks

8. ElectricSQL vs PowerSync — Bidirectional Postgres-SQLite Sync

A slightly different camp. Instead of CRDTs for text or JSON trees, the approach is to replicate the relational database itself to the client.

ElectricSQL

Belgium and EU-based team. They use logical replication on top of Postgres to stream data into a client SQLite. Client-side changes flow back and merge.

import { ShapeStream } from '@electric-sql/client'

const stream = new ShapeStream({
  url: 'https://api.example.com/v1/shape',
  params: {
    table: 'tasks',
    where: 'user_id = $1',
    params: ['user-123'],
  },
})

stream.subscribe((messages) => {
  for (const m of messages) {
    console.log(m.headers.operation, m.value)
  }
})

PowerSync

Australia-based team. Mobile first. Similar idea to ElectricSQL but with different emphasis.

When to Use Which

ScenarioElectricSQLPowerSync
Web first, Postgres onlyFirst choicePossible
Mobile first (Flutter, RN)PossibleFirst choice
Days-long offline then mergePossibleFirst choice
Notion-style tree editingNot a fitNot a fit
Simple row-based SaaSFirst choiceFirst choice

The limit of this camp: concurrent text editing and tree moves, the CRDT-specific problems, are not their domain. That is Yjs and Loro territory.


9. Tinybase — A Tiny In-Memory DB with Optional CRDT

Tinybase is the solo project of James Pollack. The pitch is "a 10 KB in-memory database with optional CRDT." It grew quickly between 2023 and 2025.

Why Tinybase Is Interesting

Tinybase Skeleton

import { createStore, createMergeableStore } from 'tinybase'

const store = createMergeableStore('store-1')

store.setTable('tasks', {
  t1: { title: 'Read papers', done: false },
  t2: { title: 'Write CRDT post', done: false },
})

store.setCell('tasks', 't1', 'done', true)

// Merge with another client
const otherStore = createMergeableStore('store-2')
otherStore.setTable('tasks', {
  t3: { title: 'Code review', done: false },
})

const merged = store.merge(otherStore)
console.log(merged.getTable('tasks')) // t1, t2, t3 all present

Tinybase is not a replacement for general-purpose CRDT engines like Yjs or Loro. It is a smaller, simpler, different camp.


10. Picking an Engine — A Decision Framework

The following decision tree is my recommended priority as of May 2026.

Collaborative Text Editor (Notion, Linear, Slab Style)

  1. Yjs plus TipTap, BlockNote, or Lexical — fastest to start, richest adapter ecosystem.
  2. Loro plus ProseMirror — worth evaluating for new projects in 2026, with the most correct algorithm.
  3. Automerge 3 — if JSON-document sync is the heart of the problem.

Hosting: Liveblocks (fastest), Partykit (cheapest), y-redis self-host (most freedom).

Mobile Offline-First Apps (Healthcare, Logistics, Field Work)

  1. PowerSync — Flutter and RN SDKs lead.
  2. ElectricSQL — candidate when the backend is Postgres.
  3. WatermelonDB — Nozbe's RN-first alternative; surprisingly stable.

Hosting: Supabase plus ElectricSQL, or your own Postgres plus PowerSync.

Instant-UI B2B SaaS (Linear Style)

  1. Zero — the most interesting choice for new 2026 projects, if ZQL feels right.
  2. Replicache — stable 1.x, used by large companies.
  3. Liveblocks plus an RDBMS — when UI collaboration is the only requirement.

Simple Multiplayer Side Projects

  1. Liveblocks — running in 30 minutes.
  2. Partykit — when you are already on Cloudflare Workers.
  3. Tinybase MergeableStore — the lightweight camp.

Guide by Data Model

Data modelFirstSecond
One rich-text blobYjsLoro
Block tree (Notion style)LoroYjs (awkward)
JSON documentAutomerge 3Loro
Relational rowsZeroReplicache
Plain LWW key-valueTinybaseReplicache
Mobile SQLite mirrorPowerSyncElectricSQL

11. Local-First in the Korean and Japanese Markets

The Korean and Japanese SaaS markets started actively adopting local-first patterns in 2025 and 2026.

Korea

Japan

What the Two Markets Share


12. References


In the next post I will pick two or three of these engines and build the same collaborative tool with each, hands-on. Not the algorithms — the corners of real code, where the actual surprises live.

Comments

No comments yet.

Sign in to leave a comment