Choosing A Sync Engine for a Local-First App In 2026

The following is a first-hand account of the process I went through when choosing a sync engine for nibfont. This is a real hand-written blog post with the opinions of a human.

tldr: zero wins, livestore is great but not a good match for my use case, electric is radioactive waste that should be avoided at all costs.


Firstly, some context about my use case. I’m building a collaborative font editor with some AI features called nibfont. In short, the requirements are very similar to Figma:

  • Real-time multi-player design tool
  • Lots of data but not millions — a project can have 10s of thousands of glyphs but not millions, a project can have 10s of collaborators but not millions. That’s a very rough layout of the real-time throughput needs.
  • Extremely fast local-first writes for the desired UX - important. I don’t need offline-first but fast writes are important.
  • Extremely good dev experience to reduce hair loss - important. I’m extremely tired of wiring up every single api call and ferry’ing the data around in redux/zustand/whatever.

First pass: Triplit

In the early days of starting on nib, I ended up landing on triplit. It’s actually quite good, a somewhat batteries-included engine that can handle both sync and real-time. It’s built from the ground up, not explicitly tied to any database flavor but works well with sqlite specifically.

I ended up abandoning triplit for two reasons:

  1. I couldn’t get it to play nice with better-auth.
  2. Unfortunately the triplit team was acqui-hired by supabase in august 2025 and triplit has become a “community” project since then. Not a good indicator for future development of the project.

Second Pass: Electric SQL + Tanstack DB

After moving on from Triplit, I decided I wanted to try something Postgres-forward. All hype indicators pointed to Electric SQL:

  • Postgres as a core primitive
  • “Bring your own existing postgres” aka incrementally adoptable - nice
  • Tanstack DB wrapper around electric SQL is promising, tanstack stuff is generally quite good

Unfortunately this was a terrible decision. Not even tanstack can save this cursed sync engine. Seriously fucking garbage.

The Electric team geniously chose long polling as the sync push-mechanism, which is an extremely slow and brittle technique that I assume died in the early 2010s. In addition, you have to “bring your own backend” to implement writes, aka set up your own backend HTTP rest-API-style endpoint(s) for writes.

There’s no fucking possible way in this universe doing writes over user-controlled http API endpoints while pushing “real-time” updates to the client over long polling can create performant sync. If I had only listened to my intuition about the former creator of gatsby joining the electric team, I could’ve saved myself 2 months of my free time devoted to fruitlessly attempting to keep my data in sync on top of Electric.

It’s possible Electric’s architecture could work and I and Claude are both too dumb to hold it right, but for now I’m declaring this cancerous — counter-productive to me reaching my goals.

Third Pass: Livestore

Next I took to the daunting task of scraping all the Electric SQL radioactivve sludge out of my codebase and replacing it with Livestore. I really like Livestore’s proposed architecture — you can pretty much run everything directly on Cloudflare, using d1 as the backing sqlite data store. Nice.

It also works extremely fast, and the creator has a live production app built on it called Overtone that they use to dogfood Livestore.

For my specific use case, I discovered that Livestore is not a good fit for one specific architectural reason: one user must correspond to one sqlite instance. This is a big limitation when it comes to sharing data between users. It’s possible to hack it and make it work on an org level, and I believe this is something the Livestore maintainers are working on supporting better, but currently poses a limitation. Livestore is best suited at the moment for something like Overtone, something Spotify-esque — lots of data per user, very little data shared between users, extremely fast client-side reads.

Fourth time’s the charm: Zero

Begrudgingly, I decided to move on from Livestore. I first heard about Zero through Syntax FM, and a colleague at Mirage also recommended it from first-hand production experience on an app. Zero is also basically Rocicorp’s third attempt at building a sync engine so I think they know what they’re doing.

I unleashed Claude on the task of migrating everything from Livestore to Zero and had a working local set up for Zero shortly.

After working out all the typecheck errors and cleaning up some cruft, everything worked, basically flawlessly. Also the code is clean — integrates well with drizzle, client-side footprint is minimal.

One thing that originally held me back from adopting Zero earlier, why I choose Livestore first: Zero does not provide a story for real-time presence. We’re not streaming events from one client to another. I had to reach for some other stuff to implement cursor chat, which actually is okay: I now realize that real-time and data sync are two different concerns.

So, literally that’s it. Zero just works. I might report back when I get clotheslined by attempting to scale websockets, but chances are my little side project font editor will not blow up so for now I’ll consider this a problem solved.

Honorary Mentions/Other Platforms

Other sync engine projects which I encountered in my research and decided were not a fit for Nib (or for me) for one reason or another.

  • evolu - very cool e2e encrypted sync engine project
  • jazz - extremely batteries-included
  • convex - also very batteries-included

Key visual: a stroke from the Broad Nib Brush tool in nib

end of storey Last modified: