✍️ 필사 모드: CRDT and Local-First Engines in 2026 — A Deep-Dive Comparison of Yjs, Automerge 3, Loro, Replicache, Liveblocks, Zero
EnglishPrologue — 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.
- No spinners — fast operations, with UI responding instantly on the local device
- Your work is not trapped — access across multiple devices
- The network is optional — works offline
- Seamless collaboration — supports collaboration
- The Long Now — works over long timescales
- Security and privacy by default — end-to-end encryption
- 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.
- Yjs — the de-facto standard for collaborative editors (TipTap, BlockNote, Lexical, ProseMirror)
- Automerge 3 — column-oriented storage, roughly 10x smaller documents
- Loro 1.0 — a Rust-based newcomer with movable trees, rich text, fast performance
- Replicache → Reflect → Zero — the evolution from Rocicorp (Aaron Boodman)
- Liveblocks — the managed Yjs platform, Series A in 2024
- ElectricSQL — bidirectional Postgres-SQLite sync
- PowerSync — similar idea with an offline-first focus
- Tinybase — a tiny in-memory database with optional CRDT
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.
- The Long Now — CRDT metadata grows without bound. Tombstone garbage collection remains unsolved.
- End-to-end encryption — CRDT merge logic and E2EE are difficult to compose. The Automerge camp is pursuing this.
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:
- G-Counter (grow-only counter)
- PN-Counter (positive-negative counter)
- LWW-Register (last-write-wins)
- OR-Set (observed-remove set)
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:
- RGA (Replicated Growable Array) — the foundation for collaborative text
- Treedoc
- LSEQ
- Logoot
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
| Engine | Algorithm family | Text | Tree |
|---|---|---|---|
| Yjs | YATA (op-based RGA variant) | Strong | Decent (nested Y.Map) |
| Automerge | RGA-like + hash-linked ops | Good | Good (JSON tree) |
| Loro | Fugue + Movable Tree | Strong | Strong (movable) |
| Replicache/Zero | LWW + per-row reorder | Plain text only | Row-based |
| Liveblocks | Built on top of Yjs | Yjs | Yjs |
| Tinybase | LWW-Register | Weak | Weak |
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
- YATA algorithm — Yet Another Transformation Approach, an RGA variant that performs well in both memory and CPU.
- A pool of shared types —
Y.Doc,Y.Text,Y.Array,Y.Map,Y.XmlFragment. Every shared type that a collaborative editor needs is already there. - A pool of editor adapters —
y-prosemirror,y-tiptap,y-codemirror.next,y-quill,y-lexical,y-blocknote. When a new editor appears, an adapter usually follows quickly. - 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
- Weak tree-move support. Nested Y.Map can sketch a tree, but real movable trees are awkward. Notion-style block editors that move a block to a different parent run into concurrent conflicts that are hard to resolve cleanly.
- Metadata accumulation. Older documents collect metadata.
Y.encodeStateAsUpdatecan compact, but it is expensive. - Weak numeric types. Counters and sets are not first-class. You build them yourself.
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.
- Column-oriented storage — inspired by Apache Parquet. When operations of the same kind cluster into columns, compression explodes.
- Documents roughly 10x smaller than Automerge 2 for the same edit history.
- Faster merge and load — memory usage down 50 to 70 percent.
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.
- IndexedDB adapter — persistent browser storage
- BroadcastChannel adapter — sync across tabs in the same browser
- WebSocket adapter — server sync
- Planned libp2p and WebRTC adapters
Yjs vs Automerge — An Honest Comparison
| Aspect | Yjs | Automerge 3 |
|---|---|---|
| Editor integrations | Rich | Sparse, you write your own |
| Data model | Fixed shared types | Arbitrary JSON |
| Document size | Small | Small as of v3 |
| Merge performance | Very fast | Fast |
| TypeScript typing | Weak | Strong, generic-friendly |
| Tree moves | Weak | Decent |
| Learning curve | Steep | Moderate |
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
- 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.
- Fugue text algorithm — an RGA variant that preserves user intent better in tricky cases like split-line edits.
- Rust to WASM — the core is Rust, distributed everywhere via WASM, NAPI, and UniFFI: JS, Swift, Kotlin, Python, Rust.
- Rich text as a first-class citizen — unlike Y.Text, which leans on Y.XmlFragment, Loro targeted rich text from day one.
- 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
- Few editor adapters. As of May 2026, the ProseMirror, TipTap, and CodeMirror adapters are beta. Not as mature as Yjs.
- No canonical server. There is no equivalent of y-websocket. You build the server yourself.
- Smaller community. When you hit an edge case, you may be the one opening the GitHub issue.
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.
- 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.
- Reflect (2022 on) — Replicache plus Cloudflare Workers and Durable Objects to layer real-time multiplayer.
- 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.
- ZQL — a client-side query language that looks like SQL but is reactive. Use
useQueryto subscribe to results. - Looks at Postgres directly — the server is plain Postgres. Zero layers IVM on top.
- Real-time without re-entry — when server data changes, every ZQL result on the client updates automatically.
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
- Not a CRDT. Conflicts are resolved explicitly. Concurrent text editing is your own problem to solve.
- Server is the source of truth. Offline operation is possible, but the server ultimately decides.
- Row-based data model. JSON trees and rich text live in user-defined columns.
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.
- Plain Yjs. Your Yjs code runs unchanged on top of Liveblocks.
- Native shared types.
LiveObject,LiveList,LiveMap— useful when you need simple collaboration without Yjs. - Comments and notifications. Notion-style comments are baked into the SDK.
@liveblocks/react— a pool of React hooks.- Threads and Inbox — pre-built comment UI components.
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.
- Built on Cloudflare Durable Objects — per-room persistent state plus global routing.
- Yjs integration via the
y-partykitadapter. - You write server logic. If Liveblocks is a closed API, Partykit lets you write the server.
- Cheap. Same pricing as Cloudflare. The cheapest managed CRDT option.
y-redis and y-mongodb-provider
The self-host camp. Persist Yjs documents in Redis or MongoDB.
- Cheapest in dollars, most expensive in time.
- The right answer when cloud usage is low, or when security or regulation rules out managed.
Decision Matrix
| Scenario | Candidate |
|---|---|
| Notion clone, including comments | Liveblocks |
| Small side project, Cloudflare-first | Partykit |
| Enterprise self-host | y-websocket + y-redis |
| Global edge, price-sensitive | Partykit |
| Slack-style messaging plus collaborative docs | Liveblocks |
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.
- Server: Postgres plus the Electric service. Electric is written in Elixir.
- Client: SQLite, via
@electric-sql/client. - Shape — the sync unit. A SQL where clause selects a partial dataset.
- 2024 pivot. Simplified from a local-first full-stack framework to a focused Postgres-to-SQLite sync engine.
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.
- Client first. React Native, Flutter, iOS, Android SDKs lead.
- Multiple backend databases. Postgres is primary, with MongoDB and MySQL support shipping.
- PowerSync rules. SQL-like rules define which rows sync to which users.
When to Use Which
| Scenario | ElectricSQL | PowerSync |
|---|---|---|
| Web first, Postgres only | First choice | Possible |
| Mobile first (Flutter, RN) | Possible | First choice |
| Days-long offline then merge | Possible | First choice |
| Notion-style tree editing | Not a fit | Not a fit |
| Simple row-based SaaS | First choice | First 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
- Simple.
setRow,getRow,delRow. A Redux-store-like abstraction plus reactive queries. - Small. Under 50 KB minified.
- CRDT optional. Default is LWW; bolt on the Yjs adapter when needed.
- Indexers, metrics, checkpoints. Rich out of the box.
- 2025 MergeableStore. A lightweight CRDT merge baked into Tinybase itself. You can sync with plain LWW without pulling in Yjs.
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)
- Yjs plus TipTap, BlockNote, or Lexical — fastest to start, richest adapter ecosystem.
- Loro plus ProseMirror — worth evaluating for new projects in 2026, with the most correct algorithm.
- 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)
- PowerSync — Flutter and RN SDKs lead.
- ElectricSQL — candidate when the backend is Postgres.
- WatermelonDB — Nozbe's RN-first alternative; surprisingly stable.
Hosting: Supabase plus ElectricSQL, or your own Postgres plus PowerSync.
Instant-UI B2B SaaS (Linear Style)
- Zero — the most interesting choice for new 2026 projects, if ZQL feels right.
- Replicache — stable 1.x, used by large companies.
- Liveblocks plus an RDBMS — when UI collaboration is the only requirement.
Simple Multiplayer Side Projects
- Liveblocks — running in 30 minutes.
- Partykit — when you are already on Cloudflare Workers.
- Tinybase MergeableStore — the lightweight camp.
Guide by Data Model
| Data model | First | Second |
|---|---|---|
| One rich-text blob | Yjs | Loro |
| Block tree (Notion style) | Loro | Yjs (awkward) |
| JSON document | Automerge 3 | Loro |
| Relational rows | Zero | Replicache |
| Plain LWW key-value | Tinybase | Replicache |
| Mobile SQLite mirror | PowerSync | ElectricSQL |
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
- Notion clones. Tools like Slid and similar note-takers are built on Yjs. Slid was a Liveblocks candidate but reportedly went self-hosted.
- Kakao Work's collaborative document. The doc feature that went into beta in 2025 is Yjs-based, ProseMirror plus y-prosemirror.
- Naver Memo. The spring 2026 refresh strengthened multi-device sync, with an architecture that looks like the Replicache family.
- Toss Payments merchant tools. The offline-capable sales-entry tool is a PowerSync candidate. Adoption is not publicly disclosed.
Japan
- LINE Notes. Early 2026 introduced multi-device collaborative notes. Rumor has it that they wrote an in-house adapter for the Yjs camp.
- Sansan / Eight. Business-card sync uses SQLite-on-the-edge plus an in-house CRDT, per their JJUG 2025 talk.
- Mercari Mini. A 2025 expansion attempt. PowerSync adoption is under evaluation for the Japanese offline mobile-payment environment.
- Hatena and Cybozu. Internal collaboration tools have partially adopted the Yjs camp.
What the Two Markets Share
- Security and regulation push them toward self-hosting rather than managed SaaS like Liveblocks or Partykit.
- Carrier mobile networks are robust enough that ElectricSQL and PowerSync are not as urgent as in Southeast Asia.
- Yjs dominates the collaborative editor camp. Loro is only just being seriously evaluated in 2026.
12. References
- Ink and Switch, "Local-first software: You own your data, in spite of the cloud" (2019) — https://www.inkandswitch.com/local-first/
- Martin Kleppmann, Adam Wiggins, Peter van Hardenberg, Mark McGranaghan, original local-first essay
- Yjs documentation — https://docs.yjs.dev/
- Kevin Jahns, "Near Real-Time Optimistic Replication" (YATA paper)
- Automerge 3 announcement — https://automerge.org/
- Loro documentation — https://loro.dev/
- Loro 1.0 release notes (2025)
- Fugue: A Generic Text Replication Algorithm — https://arxiv.org/abs/2305.00583
- Replicache documentation — https://doc.replicache.dev/
- Zero by Rocicorp — https://zero.rocicorp.dev/
- Liveblocks documentation — https://liveblocks.io/docs
- Partykit documentation — https://docs.partykit.io/
- ElectricSQL documentation — https://electric-sql.com/docs
- PowerSync documentation — https://docs.powersync.com/
- Tinybase documentation — https://tinybase.org/
- TipTap collaboration docs — https://tiptap.dev/docs/collaboration
- BlockNote documentation — https://www.blocknotejs.org/
- WatermelonDB — https://watermelondb.dev/
- "A move operation for replicated trees" — Kleppmann, Mulligan, Gomes, Beresford
- "CRDTs: The Hard Parts" — Martin Kleppmann talk
- JJUG CCC 2025 — Sansan and Eight CRDT talk (Japanese)
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.
현재 단락 (1/320)
In April 2019, Ink and Switch published "Local-first software: You own your data, in spite of the cl...