Our friends Johannes Schickling & James Long join us to discuss the movement of local-first, its pros and cons, the tradeoffs, and the path to the warming waters of mostly local apps.
Featuring
Sponsors
Fly.io – The home of Changelog.com — Deploy your apps close to your users — global Anycast load-balancing, zero-configuration private networking, hardware isolation, and instant WireGuard VPN connections. Push-button deployments that scale to thousands of instances. Check out the speedrun to get started in minutes.
Timescale – Purpose-built performance for AI Build RAG, search, and AI agents on the cloud and with PostgreSQL and purpose-built extensions for AI: pgvector, pgvectorscale, and pgai.
Eight Sleep – Up to $600 off Pod 4 Ultra — Go to eightsleep.com/changelog and use the code CHANGELOG
. You can try it for free for 30 days - but we’re confident you will not want to return it (we love ours). Once you experience AI-optimized sleep, you’ll wonder how you ever slept without it. Currently shipping to: United States, Canada, United Kingdom, Europe, and Australia.
WorkOS – AuthKit offers 1,000,000 monthly active users (MAU) free — The world’s best login box, powered by WorkOS + Radix. Learn more and get started at WorkOS.com and AuthKit.com
Notes & Links
Chapters
Chapter Number | Chapter Start Time | Chapter Title | Chapter Duration |
1 | 00:00 | Let's talk! | 00:40 |
2 | 00:40 | Sponsor: Fly.io | 02:32 |
3 | 03:12 | Meet our friends | 02:23 |
4 | 05:34 | Defining local-first | 04:01 |
5 | 09:36 | Why go local-first? | 16:36 |
6 | 26:12 | Sponsor: Timescale | 02:17 |
7 | 28:29 | Sponsor: Eight Sleep | 02:32 |
8 | 31:01 | What are the tradeoffs? | 15:09 |
9 | 46:10 | Fighting the browser | 02:41 |
10 | 48:51 | Overtone.pro | 07:35 |
11 | 56:26 | Sponsor: WorkOS | 02:50 |
12 | 59:16 | How warm are local-first waters? | 11:42 |
13 | 1:10:59 | Apple notes is less local-first | 05:59 |
14 | 1:16:57 | The future for "mostly" local | 13:35 |
15 | 1:30:32 | Listen to localfirst.fm | 01:38 |
16 | 1:32:10 | Closing thoughts and stuff | 01:28 |
Transcript
Play the audio to listen along while you enjoy the transcript. 🎧
We are here with a couple of local-first aficionados, and frequent Changelog guests. I think for both of you, this is your fourth appearance on our pod, so welcome back. We have Johannes Schickling and James Long. What’s up, guys?
Not much. Great to be here.
I’m super-excited to be back, talking more about local-first.
What was my third time? I know I came and talked about Actual, and then I talked about something else. I didn’t know this was my fourth.
You’re putting me on the spot…
Sorry about that.
So for a time, for a little while, we produced and shipped the React Podcast here on this network. So you were on Changelog Interviews 242, The Burden of Open Source, and then you were on the React Podcast, talking about React and Electron, which we published, and then actually Open Source, talking about Actual. So with us, with Adam and I, twice. But on the network, three times.
Got it.
Technically, with me, several times.
Nice.
And you just once, Jerod.
Fair. Good fact checks, guys.
Cool.
Fact-checking you on here.
Johannes, do you feel good about three? Does that sound right to you?
Yeah, it sounds about right. I guess one or two maybe about GraphQL, Prisma…
Yup.
Another one about something afterwards… I don’t know.
About using the Effect library. Are you still an Effect fan?
Oh, yeah. That was on JS Jabber though, I think.
That’s JS Party, which is a far superior JavaScript podcast. No, just kidding.
[laughs]
We’re just doing conflict resolution right here live.
That’s right. All of our podcasts are resolved in a CRDT… So I just use that in a way which probably showed that I don’t know exactly how it works. But let’s move on swiftly. We want to talk about local-first because we have, of course, Johannes, the purveyor of local-first.fm. And James, you’ve been doing things locally in the browser for a long time. Pushing the limits, I would say both of you. And James recently posted a post - which is what you do now; you don’t tweet anymore, you post - in which you said that after a long time of trying it, doing it, etc. you don’t think it’s the future for all kinds of apps… Which, there’s a question about - is that the case? Now, it’s a blog post in progress. Whenever I see somebody with a blog post in progress, or it should be a blog but it isn’t yet, and they’re talking, I think, “Let’s have an actual conversation, because this will help all of us think through things, and iron out our thoughts.” And so I want to start there, but before even that, I think the definition of local-first is also ambiguous, or perhaps up for debate. So maybe we can get on the same page about what a local-first web app is, and is even the word web supposed to be in there? I think it is. But Johannes, maybe start there. We’ll see if all four of us agree about what local-first really is.
Yeah. I would say that web is not a necessity of local-first. I think local-first has kind of so far gone through two rounds of a definition attempt. The first one was through the Ink & Switch essay, I think in 2019 or so, by Martin Kleppmann, PVH, and so on. And that was outlined in a set of seven ideals that define what local-first software is. And that’s along the lines of like no spinners, the network being optional etc. None of that is really about any platform. I think at the end of the day, software runs wherever we use a certain device, and at some point, it might run wherever. So the web is just a very common platform.
And then the second attempt to define what local-first software is was last year, or technically, I guess, this year, from Martin Kleppmann at the first Local-first Conference, where I think he tried to simplify it a little bit more, what local-first software is, where I think he said it along the lines that a computer that you’re not aware of even existing should not prevent your software from running. And I would say that this is – we can take it apart, what that definition means, but I think it’s kind of intuitive.
And given that a lot of software runs in the browser, I think that’s where particularly our mind goes… And I would say it’s particularly hard to build local-first-grade software in the browser. It’s somewhat possible, sometimes not fully practical, and I think we’ll go deep into that. But local-first is not inherently bound to the web.
James, anything to add or subtract? Agree or disagree with what he’s saying there?
[00:07:44.25] No, I think that all sounds good. I think the web is definitely kind of anti-local-first, in some ways. The community, it feels like we’re kind of pushing the – that’s why we have to push the boundaries, because it has a lot of boundaries around doing things locally. Because, I mean, honestly, it began as this client-server model, which is a really powerful model… And so we’re trying to figure out how to kind of pull it back from that. But no, I totally agree. I think these kind of terms are helpful for conversations, because it’s a very simplistic term. It’s like a codename almost. But these things always naturally are actually pretty ambiguous and difficult to nail down specifically… And I think it’s worthwhile to have conversations to actually define the nuances a little bit more. And they can also be under the umbrella of local-first. I don’t think we need to mince words too much, but it is worthwhile to kind of take a step back and actually talk about what that means exactly to different people.
Right. And I might also add that I think local-first is often seen as sort of this binary thing, but I think that’s, at least as of today, not too helpful of a perspective. I’d rather encourage people to think about local-first as like an aspiration and a spectrum that you can take many steps in that direction. Would you have reached all the seven ideals of the local-first manifesto? Or would you fully already nail the definition of like “If another computer stops working, does your software stop working?” Probably not, but you’re already well on your way there. Software like Linear, etc. is not local-first by definition, but it’s way further along the lines there, compared to other software. And I think that’s what it’s all about, aspiring to building better software… And I think local-first is a way how we can think about getting there.
When you have the question, “What?”, you have to follow up with why. So we know what it is now. We all agree. Why go local-first? Why aspire to this spectrum? Why aspire to the binary? Why aspire at all towards this local-first direction?
I can start with why I – so I sort of naturally fell into this around 2017, which was around the time – I think the essay came out like a little bit later, after I had started going down this path, and it sort of crystallized a lot for me… But yeah, when I was building Actual, which is a personal finance app that I built and tried to sell for a couple of years, it actually met most of the principles of the original one, where it’s actually completely local-first. Literally, all of your data is local, and the server is a super-dumb server. And in that architecture, you can even like end-to-end encrypt, which felt appealing. I think it was more appealing idealistically… Or like ideologically appealing, because it was just like “Hey, it’s your personal finance data. It sounds cool to encrypt it.”
I think a lot of my evolution from this is like nobody really cared that much about that, and so I’m sort of reeling back from some of the deep research I did back then. But anyway, the main reason I did it was for performance, and just like simplicity. Like, it just felt really cool to just like write SQL queries, and have this SQLite database totally available to your entire local machine, rather than like an HTTP request that came back with like most of the fields, but some of them were missing, because they needed to join some table that was on a totally different sharded database in a separate place, and so then you have to do a follow-up HTTP requests… And GraphQL sort of follows some of this, but GraphQL just feels like super-overly complex. And when you need to do mutations with GraphQL, it just like really locks you into this specific format. And then you’re always waiting on loading spinners, and things like that. And it’s just like this super-compelling simplicity of “Hey, SQL query, get my data back within like five milliseconds, render it, and then subscribe to that data.” And it’s all completely local, and you don’t need a web socket to a remote server that could die anytime now. So that was my main motivation. And I think that might hold for your reasons, Johannes. Is that right?
[00:11:45.07] Yeah, I very much subscribe to what you’ve already said. I think there’s two major perspectives for me. One is the perspective from me as a builder who wants to build a product for end users… So what do you want the experience, the end user experience to be. And obviously, performance is one that very quickly comes to mind what I want the app to feel like. Like, it should be a great user experience. And I think local-first - that’s a really high bar. This is where software like Linear etc. is so – that makes it so clear that this is just like a high-quality app, and local-first helps you get there. But the other one is from a developer experience perspective, and not just even from a developer experience perspective, but also, like, you’ve mentioned the word ‘simplicity’. I think this is what typically ruins both the user experience and the developer experience, that we’re just drowning in complexity. And local-first for me was sort of like a promise for like “Eventually, we can do much better.” We can build more ambitious apps, in a way that is actually simpler to build for the developer. And this is a promise that I still strongly believe in. We’re not quite there yet. We’ve already made a lot of progress in that direction, but that is what it’s for me all about.
We’re drowning in complexity, typically, the more ambitious of an app we are building, and local-first gives me hope and gives me a promise that we can do a lot better, that we gain simplicity for some category of apps. And this won’t hold for all category of apps. This is where I also strongly agree with your point that you’ve shared. Local-first is not the panacea for all apps. But for some apps, it can be a much better trade-off, and this is what’s got me so excited about it.
What category of apps do you think that applies to? Because when it comes to simplicity, I think the mental model for a developer that I operate a server and a client makes a request to my server, and I do whatever I’m going to do and then I respond, and the client renders it - to me, it’s hard to get more simplistic than that… But that, admittedly, is a very basic application. However, there are some applications where it’s like, you’re reading a newspaper. Does it need to be local-first because the actual newspaper content is not in the device, it’s on the server, and so what good is it without the server? So I’m sure there’s like – I mean, you keep mentioning Linear… It’s probably like hit it out of the ballpark use cases, there’s probably pretty good use cases, and there’s probably like maybe what James has been finding is like “Well, is it the future for all apps?” He doesn’t think so. So in your definition, Johannes, or your thoughts, what are the greatest local-first apps? And then what are some good ones? And then maybe we’ll get to ones where it’s like “Yeah, it doesn’t really make sense.”
Yeah, so I don’t disagree with James, that – it is not the future for all apps. I do think it could be a better future for some apps. And so how I would categorize this, as a starting point at least, is like anything that really centers around my data as a user, or as a small group of users.
So for example, a note-taking app. I think that’s a very prototypical example for where local-first makes a lot of sense, where it’s simple to build, and where it also from an end user perspective makes so much sense, because all the data that’s ever created in this app, I probably created, I’ve written down by myself at some point, or dictated etc. All the data is coming from me, very opposed to like a newspaper where someone else has written everything, and I’m never going to read all of it. So that I think it’s an anti-use case for local-first, anything that feels like a social network, or a newspaper etc. Anything where a individual user, a small group of users have created some data, I think that is a very good use case. This is where you have – if you think about the data as a graph, it’s a very highly connected, dense graph, and it’s very easy to, in terms of the quantity of the data, that it actually fits on the devices that we’re using.
[00:16:13.27] So this is like a rough idea of what makes a good fit for a local-first app, but then it also scales a bit beyond that. So for example, if you want to not just work on some data by yourself, but maybe in the realm of a smaller group, let’s say your family, or let’s say your small team, your medium team… And the larger it gets, the less of a good fit it possibly is for local-first, since trust is another matter. You’re probably trusting all the members in your family; you might also trust all the members in a small team. Once you’re like a 5,000-person company, you have legal obligations, etc. that you need to have like strict boundaries in place, and someone needs to be able to remotely delete all of the data… This is where it gets a little bit in tension. But the more it is all about like a user’s data, or a small group of users, I think this is where it’s a great starting point.
So James, you said in your post that you think it’s cool for some very specific apps, like Obsidian. You used Obsidian as an example. Of course, this is an app that’s operating on your local Markdown files, and then also building from there and doing additional stuff. You built a personal finance app in the browser, right? Or with web tech, and that sounds like something Johannes is very much defining. And you gave it a shot. So curious your thoughts on that being applicable in that circumstance, or what you’ve found with regard to what sounds like a good use case for local-first, according to Johannes, and makes sense to me.
Yeah. I mean, it’s just hard, because that kind of reasoning still resonates with me. It’s super-amazing to just like have this three megabyte SQL file. And for me personally I have maybe eight years of transactions and it’s maybe scaled up to five megabytes. Like, it’s not ever going to be remotely a problem to have this all local. And to me also, it was really compelling at the very beginning. So I actually started with web tech, but it was wrapped with Electron. It was a fully native app, so I didn’t have to be actually in the guardrails of the browser. So I literally used native SQLite. There was a SQLite file locally, on my computer. And what’s so cool about that is I could just like fire it up from the terminal and just like run some select queries.
The problem – there’s a lot of things that kind of have chipped away at that for me, that have made me kind of take a step back to kind of rethink some of it. Even for some of these apps that do seem at the upfront better, one is like the web is the distribution king. You cannot get away from the web. It was very clear early on that downloading this huge 300 – or it wasn’t 300. I don’t know, it was like a 75-megabyte Electron app, and everybody made fun of you for building this web thing that was [unintelligible 00:18:56.14] I was just tired of having to deal with all of that stuff. And also, the notarization, and actually deploying things to the native stuff - it is the worst thing I’ve ever experienced. I also built React Native apps too, which is – the biggest regret of Actual is just like overextending myself. But for every single thing that you distribute it to, you have to notarize it, you have to pay money to actually distribute it through it. You have to get a certificate for Windows. I had to get this other weird Certigo certificate chain. So it was a lot. So I deployed to the web, and I could make a bug fix and deploy it in like three seconds. It was amazing.
The problem there is like I lose the local SQLite file capability. So they do have this new technology… So I built Upstart SQL, which was like this SQLite abstraction over IndexedDB. I still think it was like super-cool. Now they have this OPFS capability with like the file system access API, so you can do real files, but they’re sandboxed within the browser. I don’t know, they might exist somewhere locally, but I’m pretty sure you’re not really – I don’t think they do. I think that they’re compressed in a weird way that you can’t actually just have a local SQLite file. They give you basically like a virtual private file system. And so you lose this ability to just like “Hey, have a local SQLite file.”
[00:20:10.12] So you have to compile Web Assembly down, and so you lose some performance. I don’t know exactly what that overhead is, but it’s slower. And Johannes, you probably have way more experience, because you’ve done a lot more of this more recently. So I’m curious what that perf is. We can talk about that in just a second.
But there’s going to be a little bit of overhead right there. Like, if it’s C running, I can fine-tune it, I can tweak it… But web always blows me away. Maybe it actually matches the native speed now. But there’s also just the fact that you – your data is still sandboxed in this thing, right? And so that was one thing, is that I lose the ability to just like locally query stuff, and so now if I want to do that - well, let’s say that I had a script, right? I’ve had this idea, that was kind of this weird idea where I think I – I saw this person on Hacker News a couple weeks ago… He had this printer that every day would like print out something. And I love that idea, and I’m actually going to do that. I’m going to have this printer that prints out things. And one of those things is going to be like my latest transactions things, like how much have we spent on food? And then I can go up and print that on my refrigerator, my whole family can see – my wife can see updates to our budget. To do that, to have a service running on my machine, now has to – it’s just really awkward. I guess it has to become a client in this whole distributed system, and so it has to download the entire SQLite file, the entire data, and then download the whole app to interface with it, and then like run those queries. Whereas if it was just an HTTP API - I mean, just like you said, Jerod, this is incredibly simple. You just make a request to the HTTP API. So it scales complexity-wise across use cases that I think are more interesting.
There’s something that I’d love to talk about later in this podcast, about what I think could be the next step for some of these kinds of things… But I do still think that for like note-taking things and things like that, it does still feel – I use Bear, which all uses like a local SQLite database, as far as I know. And then it uses like the iCloud syncing. And I love it, because it’s so fast. I can just pull it up on my phone and I know that it will come up instantly, because all the files are there. So I think there are still use cases there, but there’s still a case where I can – I guess it feels nicer, because I can just rsync my files to my server, and then I can build a little HTTP thing around it… But it just feels like this complexity that I kept having to hit, for like “Oh, there’s this thing that everybody else is used to doing… And I can’t do that now, because I’ve built this other thing”, that has good trade-offs, but I think that there might be architectures that have similar benefits, without losing some of that kind of weirdness.
And also, my final point I’ll make is that those complexity things, and like there’s these libraries now… Johannes, I need to look more at yours, but I’m scared to build on top of something, especially if it forces me to use their own APIs, their own database abstraction. I like the ones that do let me use raw SQL, but even those are like – this is a fundamental abstraction in your stack, right? If you’re a startup that blows up, and you get stuck with that, and it turns out to not scale or be bad, or the people making that burnout or go away, that is a hard place to be in. So we’re gonna have to be really – this is just like a chicken and the egg type thing, where it’s like if you want local-first to be really good and build these things, you’re gonna have to work really, really hard and long to make this a robust foundation. So that’s my initial thoughts. Happy to –
Yeah.
So in your experience building Actual, are you saying that you kept hitting these walls, or you would hit the walls if you had to scale and network and add clients, or add collaboration, so to speak; this local-first direction took you in places that locked you in, and couldn’t let you scale, or do things differently, I suppose. I’m not finding the right words, but essentially it locked you into a place or choices and you couldn’t get around them.
[00:24:03.09] Basically. That was one thing that was hard. And some of that is because I built it myself. And so if I was able to use it… There’s this amazing library, the one that Johannes has built… Is it LiveStore? Is that the name of it?
Yeah, it’s LiveStore.
Yeah. I need to check that out. It definitely would have helped me a lot if I had one already that was there, that solves some of these problems for me… But I think some of this is a fundamental piece of the architecture, where like people just wanted to build a little client around it, like themselves, and access Actual’s API. I was like “Hey, okay, we don’t have an API.” I ended up building one and you know how it worked? It was the entire app which downloaded the entire data first. And so to boot up the API the first time you run - it takes seconds. Like ten seconds. So very, very slow, and not very tenable.
And the other thing that I was gonna say is things like protected data. So if people wanted to have these roles and authorization, I think that’s something that – I think that that part I think is not fundamental. I think that’s solvable, but it is hard. If you wanted to say like “This user can input this transaction, and I wanna hide it from my spouse, because it’s like for Christmas.” Those kinds of things just got really tricky.
And just these weird things where like, okay, you’re logged out, and you stopped paying for the product, but you still have it all locally, and so people could like keep using it… It’s just like these weird things where like you log out and you log back in as a different user, but the other local data is still there… To me, I was like “I wanna be a startup that focuses on the problem.” Like, solving the actual – and I kept having to be dragged into this weird mind state of “How do I deal with this crazy, weird, mind-bending situation, that local-first?”
So I think that is the benefit of some of the why the community has moved towards this newer model of local-first, where it’s like a really heavy kind of cash where it’s still a little bit more dependent on the server and it’s not completely local-first, but it adheres to the principles where you query your data locally, but it’s not as –
I’ve got a new name for everybody. Let’s stop calling it local-first. Let’s call it mostly local. Okay? Mostly local.
You’re swimming upstream there. I think we have critical mass calling it local-first at this point.
Break: [00:26:12.17]
It seems like the difference between James and Johannes is like James is trying to build an app for production, and Johannes loves building dev tools and things to enable developers. And so you’re kind of trying – I mean, almost like James would be your eventual customer or user, because he’s talking about this LiveStore. You see this future, and you’re trying to create it, right?
Well, that’s not entirely true. So the reason why LiveStore came into existence in the first place is actually based on a predecessor project called Riffle, that I collaborated on with Geoffrey Litt and Nicholas Schiefer. Geoffrey did his PhD project at MIT about this last year, and this was like a two-year collaboration where we worked on Riffle. The entire idea around Riffle was what if you can make your app state management, not just like the backend data management, but actually your app state management - what if you could also use a database for that, and bringing reactivity etc. right into your app. So kind of going one step even further to what James landed on to using SQLite, I think typically on a separate thread, where you asynchronously work with that SQL database. We took it one step further, and we saw SQLite being so fast that you could actually run it in the main thread. That’s a different can of worms, technically very, very interesting, and it was also quite a challenge to build.
But the reason why I got involved is actually I took a bit of a step back from Prisma, where it was all building DevTools…
I wanted to get back into the shoes of building my own app again. So that was actually, in terms of the chicken and egg, where LiveStore is maybe the egg, but the chicken that was also needed was me working on my own app, and that’s called Overtone. Overtone, I think we’ve talked about that on a previous time, is a new music app that I’m building, sort of like a third-party client for wherever your music lives. So think about it like what Superhuman is to Gmail, Overtone is to Spotify, or to your own music collection, to YouTube Music, to Bandcamp etc. And that was my primary driver to even want something like Riffle, and that now led to LiveStore.
I’m developing both LiveStore and Overtone in tandem. Overtone is still the most demanding use case that informs all design decisions around Livestore, but I’m not doing this in a vacuum, even though I am with one foot in the DevTool building situation, but with the other foot I’m firmly in the app-developing situation… But I think where I can now also subscribe to the point you were making - I am less strict about making harsh, time-wise trade-offs in favor of shipping earlier. I’m taking a more long-term-minded approach, and I’m making design decisions around the technologies and around the product that might cost me a couple of months, maybe sometimes years, in terms of actually shipping everything, where I take a more long-term-minded approach. In your typical startup, things about how can we reduce the scope for initial MVP and just launch it, and this is where I take a different trade-off.
Gotcha. Okay, that makes a lot of sense. So effectively, what we have is a different way, a new way of building for the web, which is the opposite way that most of us have been building for the web. We call it client-server, but we really are server-oriented, and the client does things, and the server is the source of truth in all of that. And that’s a new and perhaps better way of doing it. However, there’s a whole bunch of groundwork that has to be laid, because you’re basically pioneering… And what James, maybe you have found as you pioneered through this on your own, with Actual, is like there’s lots of different gnarly, nasty problems that have to be solved in order to do this. Stuff that us architecture astronauts don’t really think about when we’re just on podcasts, talking about new paradigms in a different way, is like “Well, what happens when somebody cancels their account in this local-first?” You ran into that in reality, and you’re like “Oh, I have a whole new problem I have to solve.” Whereas on a traditional web app - we’ve been there, we’ve done that. You can just look it up, or whatever you’ve gotta do, to be like “Oh, here’s what you do.” And those trade-offs for you, James, today at least, or over the last few years, have been not worth it. Fair?
[00:35:35.03] Yes, I think that’s fair, and I do think that there are – I want to make sure that my points are… My main concern is that some of my problems are just completely inherent. Like, if you are assuming that you’re gonna query your data locally first, and if it’s not there, it can fall back to the server, even if that, there are things inherent into this that I don’t think are completely solvable. And I don’t mean that that means that it’s dead in the water or it’s a bad idea, but that there are problems that I think I don’t see mentioned, or that I think people say “We’ll get there. We’ll figure it out.” But to me, it’s an inherent part of it, so it’s less that – there’s all these things that I think… And I don’t mean to put cold water on this at all. I do think that LiveStore and other things can make this significantly easier and solve a lot of the hard problems, and there will be many apps built on things like that. But I think there are a couple of trade-offs here that just need to be kept in mind as well, that might make it hard for other people.
Right. Newsflash, it’s not a silver bullet.
Yeah.
And not only are there problems that haven’t been solved yet, but you think that, just like anything, there are trade-offs, and there are problems that you don’t think will be solved. The API one is one I hadn’t really thought about. Johannes was kind of shaking his head in the affirmative as you talked about it. Is that a solved problem, Johannes? Like, you have client-first apps… How do we build a generic API for a client-first app like Actual, for instance? Is that a solved problem now?
When you say “How do we build an API?”, I’d love to hear a clarification of that, since one idea around local-first apps is that you actually – you should ask yourself, “Do we still need an API?” It’s all about the client-side experience at the end of the day, or some other things that you need to build. Maybe you want to send an email if a certain thing happens etc. and you might want to do that on some server-side thing. But one of the ideas of local-first is trying to nudge you away from that very API-centric way of thinking of an architecture. If you’re building, let’s say, a calculator app that just happens to synchronize between your phone and your tablet, then you could try to think about that as a “Oh, we’re going to have an API here”, etc. But if you just start out working on that in a single-player experience, you would never think about “Do we need an API for a calculator? No, we just built that thing client-side.” And now, if you have that as a starting point and you want to synchronize data between your different clients, you don’t necessarily need an API for that. You just need a transfer mechanism between the clients, and that’s rather where the local-first idea comes from.
That being said, you can still interact and integrate with APIs, but I think with local-first, you rather think about “How do I synchronize the data?” and less “How do I request-response with an API?”
Sure. Well, we don’t have to invent hypothetical API scenarios, because James, your customers were asking you for this, right?
Yes. They’re like “I’m in my terminal, and I want to just write this script that dumps things out, and put it on my second monitor.” I think maybe it was just like “Maybe this is –” I can’t paint too broad of a stroke here, because I think maybe this is one app that just would have benefited from a standard API, because people wanted to query the data, they wanted to write scripts on the remote server to set up their own notifications for when a transaction came through that was too much… So I know in this specific case it would have been nice to have an API.
So the way how I’m starting to think about scenarios like those is similar to how we’re using Git. I think we’re thinking less about Git as an API endpoint that you correspond with, but you’re thinking about Git as a semantic protocol that you use in a very specific environment, in this case how you evolve your code repository in a semantic way. And I think the same analogy could have also hold true for your scenario.
[00:39:47.06] So obviously, this would have taken quite a bit more effort to define and develop this, and so on, and build SDKs for the various target platforms etc. But I think from a mental model perspective, there’s nothing inherent that this needs to be an API. I think the synchronization mechanism still holds true to this, and this is where Git is sort of a very commonly-used synchronization mechanism. And I would like for apps to embrace the sort of “more Git, less APIs” mindset, where often that can be more declarative, instead of APIs often being very hand-wavy in an imperative way. But yeah, it will take time to get things off the ground there, and kudos to you again for just putting in so much effort in the pioneering work that you’ve done over the years, and particularly also targeting the web.
I think the web is both like a blessing and a curse. It is a blessing that it can already do all of those things that it’s possible with right now, the ubiquitous distribution you’re getting with the web etc, but it also lags behind in terms of capabilities that we’ve already had for decades on more native platforms. I’m very bullish on the web even catching up with those native capabilities. I just want to add a few points on what you’ve mentioned before in regards to file system support etc.
So you’ve mentioned OPFS as this origin private file system, which I think by definition is non-accessible to the user. I think the current way how it works for example in Chrome is there is actually some folder somewhere, I guess under application support or library somewhere on macOS or in different places where you can actually see the real files, but this might just be an implementation detail. It’s not meant to be user-accessible. However, also in Chrome there is a – I’m not sure whether it’s even [unintelligible 00:41:50.11] but there’s a separate API with mostly the same API surface, where it lets a user actually mount a real file or a real folder system from their actual hard drive into the web app, and you can read and write from it. So with that you get real files in your app, and those files don’t go away if something happens to the web app, unless the web app decides to delete it. But this way we’re already taking another big step towards native capability apps, at least in Chrome for now. I’m not sure what the current status with other platforms, with other browsers… But stuff like that gives me a lot of hope. If I’m now wearing my application developer hat for Overtone, what I’ll probably do is like I love those capabilities, and I’ll embrace them for the given browsers. So I’ll probably say like “Hey, if you want to use the web version and not the desktop version, and if you want to have the most advanced experience, then please use Chrome, use Arc etc.” And if you use another browser, then I’ll diffuse the capabilities a little bit, and hopefully in two years Safari and other browsers have caught up.
So I’m more long-term minded on this, and I don’t look at a feature and say “If I can’t have it on all platforms, I’m not having it at all. I’m rather like opting into features, progressively enhancement way, step by step.”
Yeah, I had forgotten that. I saw that was released, and I think you have to – there’s a notification that pops up for the user, and then they just have to say Allow.
Exactly.
And that’s great. I love that. I think that moving towards those kind of models for apps that this works well is going to unlock a lot of really, really cool stuff. I think that that is a huge blocker; that was one of my biggest gripes, and so the fact that that is starting to be solved is amazing.
[00:43:45.09] I think overall the web is sort of like in this weird spot, where the web has traditionally always been like a website distribution mechanism, but we all want more and more app-like experiences in the web, but there isn’t really this binary way of like flicking on the app mode in the browser, and everything by default is treated as a website, and that makes for a worse experience, even so that Safari takes some pretty strong steps in that direction. If you don’t visit a website for I think seven days or so, it just wipes all of your data, which can be very counterintuitive, and very much I think hampers the trust that you can put in web apps, making it, again, sort of like this self-perpetuating prophecy that “Oh, it’s just a website and you can’t have real apps.” And I guess the closest we have in that regard is that you pin something to your doc bar. That actually does change the defaults quite a bit… But yeah that’s sort of with my web optimism hat on.
Yeah. I think some of my reaction, and pulling back and evolving my ideas, I think are founded in like good stuff. Some of it is just that I was just so burned so many times on things that aren’t a problem anymore that I’m still kind of evolving my ideas… But – so I built Absurd SQL, which was SQLite on top of IndexedDB before all of this other stuff was available, and like there was definitely just like a time when I loaded up Actual and it was like “Okay, log in.” Or I think I was logged in, but none of my data was there and I had to redownload it. And luckily, the syncing stuff was nice, because I’m mostly online, so I didn’t really lose any data. But it was just a really bad taste in my mouth to be like “Man, the web just sucks.” Because it just blew away – all my IndexedDB was gone. I guess my disk was running low on space, or something.
Now, to be totally clear, like you were saying, Johannes, I think this has all been mostly solved, and is way, way better, especially if we’re starting to use real local files. I don’t think the OPFS even suffers from this, but… Yeah, the web has a lot of trust issues, I think, with people; or people have a lot of trust issues with the web.
Well, it’s an attack vector, right? It’s networked. Obviously, there’s gonna be lack of trust.
Yeah.
Also, it’s not in your control, because the browser vendors control the browsers. And so…
Right. It sounds like too both of your perspectives push against what the browser wants to be for an application. For example, it doesn’t seem to want to be local-first. It wants to be connected. It wants to assume the thing it’s rendering is connected, or wants to be connected. So you’re kind of fighting upstream to the platform, essentially, that you’re building against. So you have to get better buy-in from browsers.
I wouldn’t subscribe full-heartedly to that statement. I think this is mostly a matter of like the hive mindset that we’ve developed in the web over the last decades, really. And I think this is also where the local-first mindset gives you a bit of like a bias counter to that point… Since you can actually build a lot of web things where you treat the network as being optional. And I think if we put ourselves, at least temporarily, in the perspective of a mobile app developer, I think this is where it’s much more common that you want to build for an assumption that a user doesn’t have connectivity all the time. And the way how you build the best experience in that regard is by really treating the connectivity as an optional thing that enhances the app. And you can very much build a web app in the same way. We have a lot of ingredients that I think are highly underused in that regard… So we have service workers etc. And by just forcing you and constraining yourself in the way how you build the app, where you say “Actually, I separate every network interaction behind some little surface that I have under my control in my application. And I want to make sure that the application may be initially installed similar to how you install an app from an app store.”
[00:48:07.19] And from there, the app is functional. From there, the app is enhanced with network. Both enhanced in terms of data synchronization, loading media etc. and also loading new app updates. It’s very feasible to do that in the web. I think it’s just not really helped by major frameworks. Something like Next.js etc. doesn’t help you at all with that.
So I think it’s mostly a mindset thing… And the mindset thing is also holding back the technologies that are being developed, and the lack of those technologies don’t evolve the mindset. So I think it’s a bit of a chicken/egg problem in terms of the mindset, and we see the most development coming really rather in the mobile world, where those assumptions are much more common.
I’m fascinated by Overtone then as a local-first application, because - I mean, it’s playing my Spotify playlists for me, right? Like, how is Overtone useful in a local-only context, in an offline mode, that would make it a great local-first app?
Yeah. And I think that’s actually also raising an interesting question in regards to local-first, that it’s, again, not binary. That it’s either fully local-first, or it is embracing all the APIs. Overtone is a great example where I need a hybrid. I do want to integrate with all of the different music services, music sources that you might have… I also want to support, or I do support things like RSS etc. And there’s also a licensing question and a copyright question that comes into play here. Some data I am allowed to download, some data I’m allowed to actually cache or store locally, some other data I’m just allowed to stream on demand… So for example, if I’m playing something from Spotify, I obviously can just stream that, but I can’t download it. Whereas if I’ve bought something on Bandcamp, that is something that I actually do want to bring on my hard drive. So it really depends on the kind of situation there.
But I’m building this in a way that gives you kind of the best of both worlds. That keeps local whatever you can and you’re allowed to keep local, and that’s kind of de facto yours. Stuff that I’m buying from Bandcamp or from other places, or stuff that I already have in my Dropbox, etc. There shouldn’t be any reason why I shouldn’t be able to use that on my hard drive or on my computer while I’m doing a road trip. Whereas there’s other data, or other kind of content where I might already have seen the metadata, and that metadata should still be accessible to me. For example, that I see it is like “Oh, I like those things” or I have the playback history, but temporarily, until I have connectivity, I’m not allowed or I’m not able to stream that data. So it’s sort of like a hybrid mix, but typically, with a web app that is built in a more traditional way, at best you have some offline caching that breaks the moment you first click on an album, and then it doesn’t have all the data you need, and you just get a spinner.
Right.
So I’m curious on your thoughts on this, because there’s two more points that I wanted to bring up in terms of the things that I have found hard. One of them also is just like integration with services. With Actual, obviously, I want to pull down transactions data. So you have to have a server, obviously. You need to pull down the music to actually play it. So that forces you to have this kind of hybrid model. And I always found it hard to fully have a local thing, but then bolt on all of these additional services. I guess it was harder for Actual especially because it was this whole privacy-focused first thing, where it’s like it’s all privacy-focused, and yet you have to pull down your transactions from this third party thing where you log in and give them your data, and then it has to flow through my server, because I have to have a key to work with that service. So it flows through my server, so it’s not privacy-focused at all.
[00:52:13.06] So that was maybe a hard thing for me. But really, to step back even more so, I love having – I don’t know, I guess a hybrid can be… It’s just like an additional complexity, and heart, and like – if it’s all from the server, all flowing down, bolting on a new service is like… It flows through the exact same pipes that everything else uses, right? Now we have these two different models, and has that been hard to kind of like think through for you? Because for me sometimes I’m just like, too many things going on, and it feels nice to have a single model… But how has your approach been to sort of tackle that overhead?
Yeah, I mean, if you’re just building a notes app where the only person who’s gonna write down a note, and possibly you write it on your computer, and you write it on your phone, and those are connected somehow - that is much, much, much easier, and this is where you’re like in a fully isolated, homogenic, local-first world. And once you’re in that more hybrid world, this is where there’s a lot more that could go wrong… And if I would be all focused on like shipping something within a couple of months, and I need to get users etc, maybe I’ve raised VC money and I need to run faster on the treadmill, this would have been the last approach that I would have taken.
However, given that I want to build this for the long run, I’m taking a much different kind of tradeoff. Like, I want to be able to not hate my life building this app continuously. Once I’ve launched, I don’t want to just like move the problems under the rug, but I want to – if there’s something that can go wrong with a high probability, it will go wrong. And so I’m basically just making this a problem of tomorrow. And once you have more users, this doesn’t make things better. So I want to address those in a more principled way.
And yes, to answer your question - yes, it was pretty hard and it took a pretty long time to figure out principled approaches, principled solutions to those really hairy problems. And I think my solutions that I’ve found here are very tailored to this specific use case, so I’m not sure whether it’s going to be applicable to many, many others. However, what I’ve found for this works really well for me.
So what I’m basically doing is I ask myself “What would make my life a lot easier working with those external data sources?” And de facto, those data sources give you not of the git style history of everything that has happened, but they just give you API endpoints of partial snapshots of like “This is what our API things, what our systems things, the current state is.” And so it gives you like a “Here’s like the first 50 tracks in this playlist”, and then you can paginate over that etc. And that is very much built for this sort of like temporary client experience; the client doesn’t remember everything.
But what would help me a lot is if I would get more of like the git style evolution of everything that has happened in this playlist. Like, this track was added to this position, those are the information about this particular album, about this particular artist etc. And I’ve built myself a little module that basically gives me that worldview, and reconstructs a history of everything that has happened about it. So it gives me basically a changelog - no pun intended - about everything that has happened about this particular thing, about this particular playlist etc. And I basically isolate the heavy lifting, eating my vegetables on that particular problem, and once I have that changelog of all the histories, then it’s basically Redux style just applying that. So isolating, separating the problems… And that has made such a huge difference for me. And that allows me to actually do it. And this approach works for every data source that I’ve seen so far in regards to Overtone.
Cool.
Break: [00:56:26.06]
So how warm is the water then, Johannes, in the local-first world? So you’ve been taking it slow and steady in order to solve a lot of the problems. What about devs out there, maybe they’re more like James, or they’re like “I have a product, I want to get it out there. I have an idea. Maybe I have 18 months runway, but I’ve got to go from 0 to customers in 12 to 18 months.” Is it warm enough that local-first can make sense, or is it like “Ah, you’re going to have some tough sledding.”
So I would say that right now if you’re on a strict time budget, then local-first is probably not yet for you. I will probably give a different answer – I would have most certainly given a much more stricter answer along those lines two years ago. So I think right now it’s already less irrational to go this path… But given that there is still a lot of infrastructure missing, or it’s not quite as mature yet, I would say maybe in two years from now I would confidently say “Okay the water is pretty warm for those specific use cases.”
I would say the more of a specific use case you have, where you feel like “No, local-first is so perfect for this, and this will give me a strategic benefit further down the road”, for example that you build something where user privacy really matters, or for other reasons… If you have a very specific reason, then I would say actually the water is warm enough that you can swim, or do whatever activity in there. But it’s not a silver bullet yet. It’s not like the catch-all scenario yet. So I think for that you’re probably still better off with your typical three-tier web app. But it is trending in the right direction. We have so many more off the shelf technologies already there, that people can try out. There’s actually quite a couple that are just about to launch in the coming months… So the space is really getting there, and I’m very confident that for this mentioned set of use cases, I think within the coming years we’ll see it flip, that it gets easier and simpler to build apps in this way, in the medium to long term… Since at the end of the day if you build apps that have a rich client-side experience, it’s just by definition there’s a distributed system to be solved. And this is what Notion is struggling with right now, this is where many apps are actually getting worse over time. My Notion calendar doesn’t work offline etc. Those are things that it’s just almost impossible to retroactively address, where you need to choose the right architecture from the get-go. And I think this will be a better starting point in the not so distant future.
I feel the pain of Notion, honestly. I think Notion is the unique scenario where you almost want slivers of it to be local-ish first, or mostly local. I’m still sticking to that. Because obviously, you want the calendar to work. My Cal.app from the native macOS works just fine offline. Why in the world does Notion’s calendar not? And the same thing with opening Notion to write things. You cannot do that. It’s very challenging. You can render some of the things, it will cache some that stuff and you can view it, but you can’t interact with it. So basically, Notion is unusable when not connected. And that’s super-sad, because you’re mostly creating. There’s a lot of things that happen in Notion, because Notion can be very simple, and Notion can be very, very, very complex… Once you sort of add team members, and scale databases, and have permissions, and different layers, and all these things. So I really do not envy the engineering challenge they have to solve their mostly local problem, but there you go.
Yeah. I have so much admiration for the folks who have been building Notion, particularly in the early days, but also who are scaling Notion right now… And I think it’s almost like an impossible trade-off to make right now, because something like Apple software, Apple Notes etc. they have a very specific target audience, which is you who bought the computer; you as a primary user - it can be all about you. There won’t be some super sysadmin who says “No, you won’t have access to all of your notes anymore.” It’s your computer. All of that stuff on that computer is yours. Whereas with Notion, as they go more and more into enterprises, this is where they need to have certain constraints and certain guarantees that some intern might - if they accidentally got access to some confidential data, that someone can revoke it and not store it for eternity on their device…
[01:04:13.16] So it’s very, very different trade-offs, that are more in favor of an enterprise and less in favor of an individual who owns that data. And I think that’s almost an impossible trade-off to make. And even that trade-off aside, it’s just a very tough engineering challenge if you start out with a relatively thin client, and then slap on more and more caching. You’ve got to go all the way and treat this to some extent as your source of truth, that you can reasonably build an app experience around that.
And in that regard, Apple has to – if you’re a bit more generous to the definition of local-first, Apple has been the OG of local-first apps, in some way. And most iOS apps and so on - I guess Android apps as well - are much more local-first than the typical web apps.
Yeah, I think this idea of partial sync basically is kind of what you’re implying, that Notion is kind of needing this idea of like some of it is offline, but you can’t download the whole thing, because the intern should not be able to access those private files, so it can’t be one big database. So you need this partial syncing thing, where maybe it’s like multiple databases and one full database that can be individually downloaded… And that’s where I’ve seen some of the more modern - ElectricSQL I think has partial sync, and… What were the two other ones that I was just looking at? PowerSync, and… Yeah, there was one more. There’s this idea where you can download part of it… Which solves some of my complaint, where like you want to boot up this thing to use it as an API, but because of the way it works, kind of kind of what you were saying, Johannes, it’s like a Git thing where you can just like download the data; you could download just the data that you need to do the work that you need to do, and it’s like this partially-synced thing. But to me that just sounds mind-bendingly complex to figure out how that works, and I haven’t dug into how the current things work… But it does seem like a very hard problem.
Well, as a Notion user, the things I want to do, or – I want to create a new document. Like, I want to be able to note-take. I want the note-taking app to be able to take notes, no matter if I’m connected or not. I mean that’s a very simplistic… Like, don’t give me anything else. As an individual user, I can understand trade-offs that, okay, I’m offline, I can’t see the full table, I can’t manipulate data in the cloud… I get that. And there may be some users out there who are less savvy - I don’t think that’s very savvy, honestly, but maybe less savvy than that - that don’t get that and they get upset. But I want to create a note, I want to be able to do things like that, but I get the challenge there. I mean there’s so much complexity that you could have in Notion with what it does… And I just don’t even envy the engineering task to even accomplish that mission they’ve got. But they need it. They need it in certain ways. That’s why I think, mostly, some of it, certain tasks could be local-first, or local-minded.
I would encourage – if I was to say anything to the local-first community right now, I think one of the things that would get me very interested is an incremental approach to all of this kind of stuff. Because I think my problem is like - yes, you want to create a note, and you want to write that note. Notion does a crap load of other things, and you might not care about 90 percent of those things working offline. You just don’t care. But like we’ve been talking about, you kind of need to buy into this from the very beginning. It’s a very different development experience… And it can be a great development experience. If you have all your data locally, you just run your SQL queries even on the main thread, like Johannes said. But I want to just build my app the freaking way that everybody else is building it, because then I can use all the services everybody else is using, I can use all of the analytics things… That was another thing I ran into with Actual, when I wanted to use Mixpanel. I literally had to fork their client, because it didn’t work in the way that I needed it to work, because you have to run it locally; you can’t run it on the server anymore. And I remember it didn’t work in – because I was compiling Node to run any web worker, doing something weird like that.
[01:08:11.15] And of course, this third-party SDK, which assumes you’re in like Node in the server, would like work that way. So I had to fork it, fix all these bugs… And every single time it had to do something different. Whenever you walk a different path, it’s hard. And I want to build my apps, I want Notion to build their apps the way that everybody else is building it, the way that they’re normally building it, and then you can figure out how to add on the local-first thing. And it’s a really hard problem, but they should be able to say “I want to make the ability to add a note, and sync some basic properties of that note in that special-cased way, but the other things are just hitting an API.” To me that sounds interesting. I know that’s almost even harder, because now we’re back to like a real hybrid… But to me it’s like, then I can do my [unintelligible 01:08:58.07] focus on just the normal everything, and then like as we get bigger and harder, and things get harder, and I can specialize parts of the app to be local-first. That sounds interesting to me.
So for what it’s worth, a lot of the technologies there in the works right now, off the shelf, local-first-ish technologies… You’ve mentioned some of them, such as Electric; another one that’s coming out soon is called Zero Sync, by the folks at Rocicorp… Their previous product was called RepliCache… And so what both of those technologies have in common is that they lean into your existing Postgres database, that you can partially sync the data that’s in that Postgres database, onto your client, and then you have an experience where you can still locally work with the data, in either an optimistic way, or like save some of the writes offline, and process them when you get online again… That’s sort of like a sliding spectrum of what is not yet possible, eventually possible… But they’re heavily leaning into that incremental adoption story, which I think makes it much easier for people to say like “Hey, we have our existing app, we have our existing API, we have our existing, for example Postgres database… We want to build a new feature, we want to overhaul this existing feature, and for that we want to lean into sync, we want to build a real-time collaboration experience…” So it is getting there, but I think if someone does have the freedom and luxury to build a greenfield app, and who wants to really – in the same ways people are trusting Apple Notes… It’s like, you pull it up, you click that New Note button, and off you go; there’s nothing in your way. If you want to have that sort of like no asterisk experience, I think the best way to get there, and also have a great developer experience, is to fully lean into it.
Well I’ve certainly clicked on New Note inside Apple Notes and seen a loading spinner.
Oh, wow.
So I’m not sure if anybody necessarily has this problem solved… Wasn’t there before iCloud Sync, but as soon as they added iCloud Sync, now all of a sudden it was waiting for something to update in the background, and I could not enter text. That was recently, probably within the last year or so. But a one-off. Which makes me think about the state of sync. You mentioned these new tools that are upcoming. We had DHH on the show a couple weeks ago and we were talking about Rails 8’s embrace of SQLite, and some of the possibilities that it unlocks. He was very excited about the potential of having multi-tenant apps where each app gets their own SQLite database. Of course, this is how many local-first things work in the first place. James’s Actual app - you get your own SQLite database there in the browser…
And I mentioned - yeah, you could have your own little Basecamp right there in your client, and then it just syncs, and everything is good. And he said he’s very happy about that, except for that when I said “It just syncs.” And he’s like “I don’t think sync is a solved problem like that.” And I’m curious, what’s the state of sync tools, the community… Is it a solved problem? Obviously, partial sync is not a solved problem… But can we rely on the state of the art in syncing libraries and tools?
[01:12:17.02] I would say this is a heavy area of research, development, and various attempts, different tradeoffs. So probably not yet, by the time when this episode comes out, but in a couple of weeks from now I’m planning a collaboration with a friend of mine, Jess Martin. We’re working on a comparison landscape resource that compares the various syncing engines, the various local-first stacks, etc. So that tries to give you a much more matter of fact, nuanced differentiation of the different technologies… But what I can already offer as like a high-level answer to this is that there’s a couple of different approaches to syncing; some of them are based on CRDTs, which has the tradeoff that it can work in a fully peer-to-peer, decentralized approach, and then there can be also approaches where you still have a central authority. Each of them have different tradeoffs, each of them have multiple technologies building those… And it’s really a matter of what is a good fit for your application use case. For example, if you still want to run a central sync entity that has the authority, that implies a bunch of other technologies.
So things are coming along nicely. There are also a few that synchronize SQLite databases, for example, directly, so it really depends on what works well for your app… And I would say we’re a lot further along compared to five years ago, and in hopefully a couple of years from now there’s just de facto best technologies for the various tradeoffs, and you can just use that.
A couple of more shoutouts I would give at this point, where I think it mostly just works for the use case that you have, is something like Yjs, or Automerge if you’re using CRDT… So if you use something like Firebase, but you want to go more local-first, those are great options. There’s also jazz tools… So this is for greenfield apps. And then for brownfield apps, where you want to incrementally adopt it, things like Electric SQL parsing, the upcoming Zero etc. And there’s many others, but… I would say if you’re curious to build something with it, just to tinker, maybe not yet fully going to production, now it’s certainly a great time to get started with it. And for many production use cases it might be perfectly reasonable already.
I think a lot of the academic research is probably good enough, to where we should be able to have something that would be really good. But it’s hard to build out the actual – because the academic research is really complex math, and to distill that down into a product that’s actually stable, and has the trust of the community… Because they literally just need to exist for five years to make sure they’re not going to go under… That will take time.
But honestly, I did everything myself, which was – I overextended myself. But syncing was not near my top complaint. The CRDT stuff was amazing. I’m not taking credit for that myself. I just [unintelligible 01:15:20.26] It was really cool how it worked. And the research was there. And the syncing worked great. None of my complaints are about like “It was hard to sync.”
It’s just like, man, when you start syncing, all these other things come up that are weird. Not to say that I even nearly solved it, but it was cool that I was able to take some existing research and just get this thing that worked actually reasonably well for my use case. So I’m excited to see all these other things coming out.
[01:15:51.21] I think it now just takes new generations of actual experiences that are always getting a level further than the ones before… And I think step by step we can actually systematically solve and address some of those problems through off-the-shelf community packages, or just by sharing best practices, and building that tribe knowledge… And I think that will already take things very far.
And what is so nice about syncing is I think it will really be like a step function in simplifying our app stacks, in the same way as declarative you programming, such as React, Vue, Solid etc. this has made stuff that had been super-gnarly and imperative before, where you needed to use jQuery, or manipulate the DOM manually, to making everything just beautiful, declarative… And there’s an entire category of stuff you didn’t need to deal with anymore. Now, if you get that for data across your different clients, and servers, etc. that’s what syncing gives you. And this can liberate stuff so much, and that’s amazing.
James, you mentioned – before we call it a show, you did want to give some thoughts on what you think, maybe a future for mostly local community might look like…
See?
[unintelligible 01:17:07.04] I threw Adam a bone there. Open floor for you, James, just to share your thoughts on what you think might be compromised, or whatever your ideas are there.
Yeah, so my current – I’m kind of like kicking off my own research phase, which… I wanted to start it a couple weeks ago, and then kids and life and holidays now. So hopefully, over the holidays I’ll get some time to really dig in here. I’m excited about something… And again, this is not mutually exclusive, it’s not zero-sum. I fully support the local-first community. I think it is a really, really cool idea, and cool tech, and I’m surprised at how – it’s not mainstream, but I’m surprised at how popular it’s become.
At some point I was like “There’s no way people are going to actually really invest in this”, because it’s so much work to build a whole new platform. So really cool to see it. But what I am thinking now is that a lot of the benefits, at least that I was after, I might be able to get them with doing things just more on the edge. And so the way that my thoughts have been sort of evolving is that basically the way Actual worked was it ran your whole SQLite database, and it did the entire backend of the app, backend [unintelligible 01:18:15.02] was in a web worker. So it was a different process, but locally on your machine. So once you’re in the web worker, you can do – the SQL queries were literally synchronous. They weren’t even async, because it’s so faster – it’s so fast that the async overhead of doing multiple promise calls is actually slowing it down. And like just architecturally, it was just like having to – you introduce an async call somewhere, and then all the call stack has to be async. It’s just super-annoying.
So going back to Johannes’ development experience there - it’s amazing. Once you’re in that web worker, you async call into it, but then once you’re in there, it’s great. But with all of the problems that I’ve been saying - it’s hard to integrate services, and this kind of hybrid approach can be mentally taxing. I just kind of want things to work the way everybody else works, which is that you hit a server, and you get something back.
What if we do this – this multi-tenant approach is really interesting to me. And there’s a company, Turso, turso.tech, which is building out a scalable infrastructure for this, where every single person, every single request even, could possibly get their own SQLite database. So what if basically I took this web worker backend that is local, but it’s not actually local, and it’s at the closest data center point that could possibly be closest to you? So in that way, I’m still accepting the fact that there is a network call, that there is this boundary there, but it’s very close. So I’m hosting currently my website, jlongster.com, on the Northern Virginia data center, or whatever, and I’m in Richmond. So I’m a couple hours drive. And it’s about 20 milliseconds overhead.
[01:19:53.02] So I think we can assume 20 to 30 milliseconds overhead for a moderately non-fine-tuned network infrastructure. I think that could even be faster. That to me feels acceptable. And once I accept that single round trip cost, then I can move everything to my server, still have the local SQLite database and have my development experience there, and possibly there’s the SQLite syncing going on in the backend. So I’m not ditching syncing. You’re probably going to have to sync these changes across a backend distributed network, but it’s nice to now own that, because now I can do analytics, I can flip something on in just a minute, without having to get everybody to refresh their clients. I can do a multitude of clients. If I wanted a terminal app for my finances, I could curl something fast and get a boatload of charts. I just get that API, like, instantly. It crosses that network boundary which lets me do everything the way everybody else is kind of doing it.
So the same stuff might be going on, even the syncing, because you’re probably going to have to be syncing that SQLite database… Because you’re mutating it locally on that edge instance. It’s going to have to replicate that somehow, elsewhere. But if you draw the line of the app, and local-first is like - at least for the most part - we’re having this hybrid thing where it sort of depends on a server… But for the most part, a local-first app in principle is like you draw the line of the entire app and all of the data, and it’s all on your computer. And then there’s another circle which is the server, and then it’s syncing to this server.
This is like if you draw the line all around your app, but then it reaches over into the server for like one little bit, which is that edge node, and then that edge node is talking to all of the complex server.
And the more I go down this, the more it’s like “Well, I really just want to make a single request to the server. I don’t wanna have to go back and forth.” Maybe I’m evolving to be a ‘use the platform’ person, and I’m leaning to very light client apps, and now I’m starting to lean into this whole React model where it streams live updates… It’s kind of fitting that – it’s taken years to get here, but it seems like React Server Components are actually a thing now. And it is a compelling model. A long time I was like “This is the weirdest idea ever”, especially when I was local-first. I was like “This doesn’t benefit me at all.” Now suddenly if I’m flipping my position a little bit, it’s like, React Server Components are amazing, because I can run the entire app on the backend, get all of this stuff that I want, and stream in just a couple lightweight components that I want to. I do full navigations maybe, even possibly… And the thing that gets me is the ability to flip open new tabs instantly. That’s one of the things that I’m a little bit like - because things are all local, it’s slow to boot up the app. And normally, I was like “Well, you boot up the app once, and you’re digging into your finances for like an hour. I don’t really care.” But the thing that does feel nice is just to Command+click a link and open up a new window, and use my local macOS windowing system to split the screen, have different tabs, and very quickly, in a very lightweight way just spawn tons of tabs… And those tabs load in like 30 milliseconds. That can be hard to do local-first, because you’re buying into this thing where the entire thing must run locally, and so it has to boot up. It has to boot up the thing.
And Johannes, maybe I’m wrong that you can get a 25-millisecond boot-up time on a complex app… I’m curious how Overcast feels, if you’ve optimized for that. But that is where I’m going to be researching, and kind of diving into. So not to say that this is the solution either, but it’s something that I’m very excited about.
I applaud you for going down this path in the pursuit of simplicity, and I think this is why you went down the local-first path in the first place, and you’ve gotten really far. I think you saw some glimpses of reward, and where you can see working with SQLite locally etc. affords you a lot of simplicity… But you also revealed a whole bunch of problems that need to be addressed, and you didn’t have the time for that.
[01:24:05.03] I think there will probably be a similar situation there, given that a lot of the things are server side, and the constraints are not as severe as on a client that could potentially be in outer space, or something. So I think this is where our traditional knowledge as a web dev community etc. more broadly applies. However, I would say this works well for still very connected applications. I think it takes server-side applications to the next level, but if you want to use that, for example, to build your next Notion, that should work on crappy, plain Wi-Fi, then this won’t get you far, since you’ll now get even further removed from the next edge worker. I think it’s going to be a little while until you have edge workers natively in an airplane… But I think if connectivity is the slightest concern, then I think there might still be challenges… Or otherwise you need to still solve the local-first problems, since then you need to – if you’re connected, you get better latency, but there’s actually no difference between being offline and high latency. Being offline is just very, very high latency, until you get online again… And fundamentally, you still need to solve the same problems. And if your app can’t deal with high latency, if it loses everything, then either you don’t support that and you can’t write that on your node or something, or you still need to address it, and I think this is where you still need to address some of the underlying local-first problems. But overall, it certainly seems like a huge step on the server side, and I think this is where we’ll make progress from both ends. All the server side stuff is getting better, all the local stuff is getting better, and hopefully – in some cases it is already meeting in the middle. I think Turso is doing some really cool stuff there… But yeah, it’s going to take a little while.
Certainly. And I agree, it’s accepting the trade-off that on very slow connections, or on the plane, you will have that problem. So that to me is the thing that I’m kind of kicking down the road a little bit, where it’s like if I can specialize a very small, critical part of the app to be local-first, then there might be specific things that I would end up building in support there… But I totally accept that maybe that is very hard, and actually doing that later can be hard, too. And it’s a viable position to say that you do need to embrace it fully from the frontend. I’m sort of balancing that, and I’m kind of curious to see if I could get to that point.
I will say that people cared that my app was fast, and… I don’t know, I think we are a very well-connected world. And not to say that it’s not meaningful at all, but I think I’m kind of just falling back into the… I’m ditching some of that complexity just in acceptance that it might not work as well sometimes.
Two thoughts on that. Actually, kind of tragic… Just today - today is Tuesday - just in the Baltic Sea between Germany and Finland, the sub-ocean fiber connection was cut, probably by a state actor…
Oh, really?
[01:27:34.07] So that shows how brittle our “Everything is always connected” assumption can be. And it’s one thing if, whatever, your access to X or Blue Sky or whatever is taking a hit, but it’s another thing if more critical systems are being taken down. And the other way – like, you don’t even need to step into a plane, or something. Just go to a coffee shop, and use the public Wi-Fi there, and try to just do work for half an hour, and you’ll notice how all of your apps are rendered completely useless. And I think having that constant assumption that everything is fiber-grade, 5G-grade connected, I think can bite back at some point.
In Europe, for example, people use actually trains quite a bit, and trains have similar Wi-Fi compared to your public coffee shop or your plane… So it’s more ubiquitous than you think.
Well, that leads us back to trade-offs… Because while I completely agree with you, Johannes, on all that, sometimes the best app is the one that you can actually build. And I think that’s some of what James is hitting up with… It’s like, what trade-offs is he willing to take as a solo dev trying to build whatever it is that you’re currently building, James. You’re still working on actual as an open-source, right? Are you moving on to another product now as well?
I’ve pretty much – the community is fully maintaining it. I’m not really part of the community anymore… Which is great.
Cool. So on to greener pastures… But whatever it is that you’re trying to build, I applaud both of you. Johannes for pushing the industry forward in this direction, and James for tinkering and experimenting while you build, and willing to try new things, that most of us wouldn’t even… Try to try. And exploring that way, and helping us discover what might be good compromises, where it fails etc.
So y’all had me excited about the future, regardless of where it is. I feel like I’m warming up, Johannes, to the waters, as the waters themselves warm up… Keep us updated, keep us in the loop, as this tooling and this ecosystem flushes out and matures, so we can keep our listeners in the know as well. Because at a certain point I hope you’re right, and that pendulum flips, or that switch flips… Pendulums don’t flip; they swing. That flip switches to where it becomes actually easier to do it this way. Because I do think the virtues are better than the drawbacks, but I think, like you said, for certain people, at certain places, with certain apps it’s still probably too hard. So definitely, as it matures, I’m interested in hearing about that. Adam, anything else from you before we call it a show?
Notion. Figure it out. [laughter]
I think they’re on it.
To your point, Johannes - yeah, they’ve known very well about this problem, and been working on it for many years. It’s really hard to bolt it on. So yeah, it’s hard.
Yeah. A little plug on my behalf… If this is interesting to any listener to dig more into local-first, I want to plug the local-first podcast, local-first FM. It goes a lot more in depth on all things distributed systems, and all things, like various trade-offs… We had the CTO of Linear on there, we had James on there, we had Martin Kleppmann, who’s the author of AutoMerge, and A Local-first Essay on there… So if you’re curious to learn more, this might be a fun place. Very niche, all about local-first… But in case you’re curious about this, it’s worthwhile checking out.
Awesome. We will link to that, we will link to all the things. James, anything to plug or shout out on our way out?
Yeah, I’ll just say, if you’re listening and interested in local-first, please do not let me dissuade you. I think it’s super-interesting, and I am choosing slightly different trade-offs every now and then, but you get excited and you go and build… And I am fueled by proving people wrong, and so if I say something that you disagree with, prove me wrong and go build the tooling and support the community.
Johannes, I’m very impressed and supportive with all the things that you all did, with Riffle too, also, I was following that for a long time. So I fully love what you’re doing with LiveStore… I don’t have anything top of mind for me to shout-out specifically, so that’s all I’ll say.
I’m very certain that our paths will meet again. It might be a couple of years, realistically, but I’m pretty sure that we’ll get the best of both worlds.
Awesome. Well, that’s all this time… So we’ll just say goodbye, friends.
Bye, friends.
Bye, friends.
Thank you so much.
Our transcripts are open source on GitHub. Improvements are welcome. 💚