While learning about CRDTs(Continuously Replicated Data Types) I wanted to figure out how to incorporate them into a Svelte UI by synchronizing the replicated document with a reactive state. This proved to be non-trivial and this implementation is currently incomplete!
WebSocket
it is assigned an ID and given a snapshot of the document$effect
needs to find the corresponding value in the Loro doc and update it to matchEven if I did finish this some problems; Svelte stores do not have deep reactivity so the entire table has to be updated (granted if we use an each
block with ids some repaint can be avoided). Going the other direction to avoid a coarse update; the effect that updates the loroDoc has to be in a cell or row sub-component which seems an awkward division... unless I'm not understanding Svelte5 snippets...
Surprisingly, prior art like Relm's svelt-yjs uses this coarse approach as well. Same with the SyncedStore project.
What we really want is not to synchronize with a Svelte Store
(since they use Svelte4's coarse, pre-compiled approach), but rather to synchronize with Svelte5's fine-grained, run-time Runes
. These rely on Proxies to intercept mutations and invoke listeners.
One approach might be to instrument both SvelteState mutations, and LoroDoc observers to replicate changes in each other. There are some difficulties with this;
Definitely non-trivial. Fortunately I found one of the winners of the 2025 Svelte Society's hackathon; SyncroState. It uses Yjs
and not Loro
which isn't too big a deal- Yjs was actually my first choice because the environment was older and more fleshed out, the main author is a well-respected researcher and open source contributor.
I switched to Loro when I didn't want to use the prepackaged WebSocket server because of increased complexity. And the
y-provider
interface for making your own custom providers was poorly documented. Which was a mistake; you have to figure out the server from scratch anyways with Loro...
Furthermore SyncroState also implements schema validation which should help a lot in enforcing contracts in these distributed applications.
I'm just going to shelve this for now, and try again with Yjs and SyncroState. It might be worth picking this up again if Loro tips the balance.
To implement the websocket server I used a custom Node server. This complicates the build process and the debugging. SvelteKit's Node adapter produces a build that provides ExpressJs-flavored middleware to handle all your web app requests. You can pass this to your custom Node server that is built outside of your Sveltekit project.
To debug you spin up the dev mode(`npm run dev) then spin up your custom server(possibly in a separate debugger). They will serve on different ports so you can mix the development HMR server with your backend services.
To build for production you build SvelteKit as normal (npm run build
) then package the build with your custom server.
Anyways, it was a bit of a pain to figure out so it's worth noting.
Everything you need to build a Svelte project, powered by sv
.
If you're seeing this, you've probably already done this step. Congrats!
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
Once you've created a project and installed dependencies with npm install
(or pnpm install
or yarn
), start a development server:
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
To create a production version of your app:
npm run build
You can preview the production build with npm run preview
.
To deploy your app, you may need to install an adapter for your target environment.