Making a Shows Tracker with Jazz
I have been following the local-first ecosystem for a while, and Jazz has piqued my interest. I'm taking a further look by making an actual app, a Shows Tracker.
Activity
Task started
So yeah, this won't be a Solid App 😅. I've already talked about why I'm leaving Solid aside (for the time being); but TLDR I'm facing some performance issues with my current tooling, and I don't have the time nor motivation to solve them. Instead, I've decided to build this one app with a different stack. It should serve both to solve my current problem, and to see how other ecosystems are doing.
Now, when I started to think about this, there were other contenders to choose from. The first one was NextGraph, given that it has first-class support for RDF, and I've been following the project for a couple of years. However, looking at the documentation and resources, it doesn't seem to be ready yet. It would be fine if I just wanted to tinker with it, but I'm actually trying to build a "real app" here, and the point of not using Solid is to make my life easier. So I'm looking for a more mature solution.
Another project I considered was Convex, which seems promising and I've seen it recommended a couple of times. Unfortunately, it doesn't seem ideal for local-first apps, and I'm not willing to give that up. Apparently, it can be used with Automerge, but that seems more like an idea than something anyone is working on for real.
So, Jazz it is. The main thing that caught my attention is their focus on developer experience. Honestly, it's very similar to what I'm trying to achieve with Aerogel: help people make local-first apps without having to think about any of the technical details. They don't even mention "CRDTs" in the landing page! Also, their whole approach to defining schemas is very Zod-centric, and I've had a great experience working with Zod so far.
But it's not all sunshine and roses. By using Jazz, I'll have to sacrifice one of my most revered ideals: Interoperability 😱. We'll see how it goes, but given this limitation I don't think I'll be working with Jazz a lot in the future, even if I like it. I'm also not looking forward to managing yet another service, but I'm totally going to self-host the backend (or "sync engine", as they call it). Thanks to that, I may be able to get something done with Solid... But for now, that's completely outside of the scope. The biggest bummer here is that I wanted to eventually bring this functionality to Media Kraken, and finally make honor to its name (it's called Media Kraken, not Movie Kraken). But that will have to wait. I just want to get my Shows Tracker done, and take a break from having to developing my own tooling.
On that note, though, there is yet another impediment... They don't have Vue support 😱😱. So I'm at a crossroads now. Should I use React, and see what life is like on the main stage? (even though I've used React before, and I don't like it). Or should I give Svelte a try, which I've been wanting to do for a while? Honestly, I don't know. Maybe this will be the first part of my experimentation, to try a bit with both and see how things go.
It hasn't even been a month since my last update, and here I am writing about how I am already using my Shows Tracker made with Jazz in production. Yes, you've read that right, in production. So TLDR, Jazz is very cool and life really is better using React :/.
Here's the long version:
First of all, I'll have to resolve the tension I created in my last update: React or Svelte? Well, after having looked into it, I decided to go with React. First for the obvious reasons: it would be nice for once to use what the cool kids are using (sorry, even I felt the cringe writing that). But also, after reading the documentation and lurking in their Discord, it's pretty clear that React is the most well supported. I actually discovered there was a Vue version before, and you can even use it with VanillaJS. So I'm pretty confident that I could use Jazz in my Vue apps if I wanted to. But for now, I'm sticking with React.
(Also, in case you missed it, I'm taking React more seriously given that I'll be using it in my day job as well).
The truth is that I've felt a real improvement using React and Jazz. Let alone the fact that I'm writing this update in a month (after struggling for months using Vue/Solid), the output produced by LLMs is also a lot better. I have also been playing with Cursor's Composer model, and I really like it. Of course, I still can't get AI to sort imports reliably, but it is getting better.
So yeah, I'm pretty happy with the way things are going. Although I'm also a bit sad that I won't be able to use my favorite tools. But if I look at it objectively, and ignore sunk costs, this is better.
It's not all sunshine and roses, though. My main gripe with Jazz is, of course, interoperability. I was already worried about this in my last update, but having learned more about it, I'm even more worried. If you read their FAQs, you'll notice a very appealing sentence: "We're designing the protocol as an open specification". The problem with that is... that I'm not convinced it's true 😅. I haven't been able to find anything about this "open specification" outside of that sentence, and even asking in the Discord community (which has been super helpful otherwise), nobody knew anything about it.
Other than that, I had a few bumps setting up a sync server and the authentication, but in the end it turned out very simple (you can check out the source in GitHub). The documentation is also a bit short, and I had to dig through source code and tinker to understand a couple of things. But as I said, the Discord community is very helpful, and I guess this is to be expected when learning a new technology (not all documentations can be as good as Laravel or Vue's!).
In summary, my initial hypotheses are proving to be true. Jazz is really cool, and the DX is great (I'd even say magical). Life is a lot easier if you are using React. And there is still nothing that comes close to Solid in terms of vision and values.
So, where does that leave me?
For now, I'll continue working on the app. I am using it in production, but if you give it a try you'll realize that it's still very rough. The UI in particular sucks (specially in Mobile), since I've mostly vibe-coded it without paying much attention. But I'm quite happy with the data model and the architecture. After I spend some time on the project, I may even call it a "real app", not just a Personal App :). I also have some ideas to take advantage of Jazz in my Solid Apps, thus having the best of both worlds. But I still have to decide how much time I want to dedicate to Solid with the way things are going.
Turns out I was a bit too quick in publishing my last update on Jazz, because indeed I was using it in production... but I hadn't used it enough to find some of the problems. Now that I have, Jazz is not looking so good anymore 😅. Or rather, I don't think it's looking good for my use-case (I still think it's very cool!).
So, what's the main problem? Turns out, Jazz and I weren't on the same page. When I started working on this app, the architecture I had in mind was the same I've been working on in my Aerogel framework. Which in short is "a local-first app that syncs with your Solid POD". Of course, Jazz doesn't support Solid, so I was building "a local-first app that syncs with my self-hosted sync server". But turns out, what Jazz wants me to build is "a collaborative app that syncs". It seems like a very subtle difference, so let me clarify what this means.
The way I am implementing this in Aerogel is that once you connect to a POD, all your data is downloaded to your device. Then, whenever you make changes, these are uploaded to the POD (and subsequently downloaded by any other device that is connected). In Jazz, instead, the only thing that syncs is the data you are currently using in the app. Turns out that a Jazz store is not a single CRDT, but a collection of connected CRDTs (or Co-values, as they call them). And so, the only data that syncs is the CRDTs you're "subcribed to". There also isn't a way to track a global "sync status", which complicates the UI I would like to build (in Aerogel, I have a status dot displaying whether you're fully synced or you have some local changes).
This makes a lot of sense for collaborative applications. But in my use-case, this isn't great. What this means in practice is that whenever I logged into a new device, I would get the list of shows in my collection (because I was listing them in the home screen). And that was available offline, so I got the impression that everything was working. But then I discovered that opening a show that I hadn't opened in that device, it would be empty! That happens because, as I've mentioned, in Jazz the only data that syncs is the one you're using, not the entire store.
I mentioned before that the Discord community had been very helpful. For this particular issue, I got confirmation that it was working as intended (my approach was refered to as "sync the world", which Jazz doesn't support). The only suggested workaround was to subscribe to all the data manually. But when I tried, I ran into the same performance issues I had seen in my own approach. And nobody seemed to have any further solutions after that 🤷.
And there is yet another problem with Jazz. I am using the jazz-run cli for my self-hosted server, and I hadn't paid much attention to it, but you'll notice that it doesn't mention anything about authentication. That's actually a separate service, which I configured using Better Auth. And I got the impression that it was safe because I'm configuring my app so that only signed up users can connect:
<JazzReactProvider
sync={{
peer: `wss://${MY_SELF_HOSTED_SYNC_SERVER}`,
when: 'signedUp',
}}
>
<App />
</JazzReactProvider>
But then, it dawned on me... What's keeping other people from creating an app using my self-hosted server for syncing, using their own authentication mechanism (or no authentication at all)? Turns out, nothing 😅. I also asked about this in the Discord community, and the solution I got was to restrict the allowed origins at a network level :/. Which of course, should work in the Web, but is very easy to work around in a mobile app or even using a proxy server.
Now, none of these two problems are impossible to solve. The syncing/performance issue can probably be worked around using Jazz in a clever way. And the sync server could be guarded implementing a hand-shake before initiating the websocket connection. But this shows me that, clearly, Jazz and I aren't on the same page. The whole point of using Jazz was to forget about the syncing mechanism, and focus on building my app. But it doesn't seem like that's going to happen. If I'm going to have to tinker with syncing and performance woes, I'd much rather go back to working on my framework (and get interoperability back)!
So I think that marks the end of this experiment. I still think Jazz is very cool, and I'd recommend it if you're building a collaborative app or you don't mind the limitations I have mentioned. The DX really is magical, and using Jazz has raised the bar for what I'd like Aerogel to become (though I'm not sure it ever will).
Though I have to wonder, maybe I'm being too idealistic? If you're reading this, I'm curious to hear what you think :). Shoot me an email and let me know.
Task completed