Skip to content

Commit 831bd46

Browse files
committed
Update blog post headings for consistency
1 parent 558fe12 commit 831bd46

2 files changed

Lines changed: 20 additions & 24 deletions

File tree

  • apps/web/src/app/(landing)/blog
    • scaling-local-first-software
    • why-the-world-needs-local-first-apps

apps/web/src/app/(landing)/blog/scaling-local-first-software/page.mdx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Building local-first apps is already a challenge, and making them scalable is an
2121

2222
---
2323

24-
# Where it all began: a mission to restore ownership
24+
## Where it all began: a mission to restore ownership
2525

2626
When I discovered the concept of local-first software, I immediately knew I didn't want to build apps any other way. Ownership is the foundation of a free society—without it, there is no freedom. Outside programming, I often speak about the state, anarchism, Bitcoin, and economics. I'm currently writing a book called [Deconstruction of the State](https://www.startovac.cz/projekty/kniha-dekonstrukce-statu), which aims to return the state to the people.
2727

@@ -37,7 +37,7 @@ To reason about when events happened and how they relate, distributed systems us
3737

3838
But here’s the catch: knowing when something happened isn’t enough. To sync two peers, we don’t just need causality—we need to know which exact events the other peer has already seen. Clocks describe order and causality, but not which specific events a peer has seen. This becomes a serious limitation in peer-to-peer systems, where events (messages) might take different paths to different nodes. Without a way to compare sets of events directly, we risk syncing inefficiencies: unnecessary retransmissions, duplicated events, or bloated metadata. These issues limit scalability.
3939

40-
# Set reconciliation
40+
## Set reconciliation
4141

4242
> Imagine two computers connected over a network, and each computer holds a set of values. Set reconciliation is the problem of efficiently exchanging messages between them such that in the end both hold the union of the two sets. If there is a total order on the items, and if items can be hashed to smaller fingerprints with negligible collision probability, this can be done in logarithmic time in a fairly simple manner.
4343
@@ -55,7 +55,7 @@ By pure luck, one of them turned out to be Aljoscha Meyer. I didn’t fully gras
5555

5656
Back in Prague, I began diving deep into tree structures. As Doug [explains](https://logperiodic.com/rbsr.html#tree-friendly-functions), a tree structure is essential for incremental fingerprint computation. Compared to B+ trees, skiplists have a key advantage: because they’re probabilistic, they avoid the complexity of node splitting, merging, and balancing. But the big question remained—how do you make that work efficiently in SQLite? Traversing a tree can require up to 20 queries. Surely that must be painfully slow, especially if done in JavaScript.
5757

58-
## RBSR with(in) SQLite
58+
### RBSR with(in) SQLite
5959

6060
Long story short, I bet on something pretty unconventional: I decided to write the entire logic in SQL. No C extensions—because I wanted it to be portable across SQL databases. With skiplists, all we really need is data traversal and minimal manipulation—no need for node splitting, merging, or balancing.
6161

@@ -69,7 +69,7 @@ It may not be ideal—ideally, we’d have a key-value WASM storage—but it’s
6969

7070
Does this fully solve the challenge of handling large amounts of data? No—and I’ll write more about that later. But for now, let’s shift focus to something else: the Evolu code itself.
7171

72-
# Rewriting Evolu: fp-ts → Effect → Evolu Library
72+
## Rewriting Evolu: fp-ts → Effect → Evolu Library
7373

7474
I've always been a fan of functional programming—but it took me a while to figure out exactly why. It definitely wasn’t because of the cryptic code or the heavy terminology. What I like the most are typed errors (like the Result type), dependency injection, and immutability. These patterns make code more composable, testable, and easier to reason about. i.e., scalable.
7575

@@ -164,7 +164,7 @@ It turns out we don’t need pipe or chaining at all—just `Result` and plain f
164164

165165
Now let’s return to the new Evolu—its local-first architecture and the ongoing challenge of scaling. That brings us to the Evolu Protocol.
166166

167-
# Evolu Protocol
167+
## Evolu Protocol
168168

169169
Evolu Protocol is a local-first, end-to-end encrypted binary synchronization
170170
protocol optimized for minimal size and maximum speed. It enables data sync
@@ -173,7 +173,7 @@ relays with each other. Evolu Protocol is designed for SQLite but can be extende
173173
implements [Range-Based Set Reconciliation](https://arxiv.org/abs/2212.13567)
174174
by Aljoscha Meyer.
175175

176-
## Why binary?
176+
### Why binary?
177177

178178
The protocol avoids JSON because:
179179

@@ -239,7 +239,7 @@ That's **272 bytes** for **16 fingerprints**, representing the entire database (
239239

240240
Again, to understand how **Range-Based Set Reconciliation works**, I highly recommend [this great article](https://logperiodic.com/rbsr.html) by Doug. Now let’s take a closer look at the protocol message structure:
241241

242-
## Protocol message structure
242+
### Protocol message structure
243243

244244
| Field | Notes |
245245
| :----------------------- | :--------------------------------------- |
@@ -257,7 +257,7 @@ Again, to understand how **Range-Based Set Reconciliation works**, I highly reco
257257

258258
It’s simple—but is it too simple? No. Let’s look at how it can support **authentication**, **collaboration**, and **partial sync**.
259259

260-
# Auth, collaboration, and partial sync
260+
## Auth, collaboration, and partial sync
261261

262262
When I started thinking about local-first auth and collaboration for Evolu, I struggled to find a model that could **scale to all use cases**. That’s why the first version of Evolu didn’t support collaboration. Rather than implement something half-baked, I decided to postpone it.
263263

@@ -297,7 +297,7 @@ It’s important to understand that while these owners are _default_ in Evolu, f
297297
The goal was to make Evolu Protocol and Relay as **generic and application-agnostic** as possible. They should **know nothing** about what applications are using them.
298298
This is the only way to guarantee **maximum privacy** and **true scalability**—as we’ll explore next.
299299

300-
## Partial sync
300+
### Partial sync
301301

302302
It’s clear that no single device can hold all the data. You can’t download the whole of Facebook to your phone. Traditional client-server applications handle this by selectively downloading parts of the data using some form of
303303
fine-grained "select"—via REST, RPC, GraphQL, and so on.
@@ -313,7 +313,7 @@ So how does Evolu scale in practice? In two complementary ways:
313313

314314
It's up to the Evolu app to know which owner should be synced for which data and when. Relays must know nothing.
315315

316-
# From local-first apps to local-first clouds
316+
## From local-first apps to local-first clouds
317317

318318
Evolu’s strict design has a surprising consequence: the **limitations** of local-first apps are exactly what allow them to **scale better** than traditional client-server applications.
319319

@@ -365,7 +365,7 @@ Let’s summarize why I believe Evolu can scale:
365365

366366
- **Peer scalability through stateless sync**: Evolu scales to virtually unlimited peers by using content-based synchronization instead of shared state. With range-based set reconciliation, peers compare data fingerprints, not sync histories. Relays remain stateless, remembering nothing about past syncs. This enables massive horizontal scaling with no centralized bottlenecks.
367367

368-
# Conclusion
368+
## Conclusion
369369

370370
Today, I’m open-sourcing what I have. The tests and benchmarks say that Evolu is ready. It’s not a production release yet—just a few details remain, like managing owners and a few other tasks tracked in [GitHub Issues](https://github.com/evoluhq/evolu/issues). I also still need to write the free, open-source Evolu Relay.
371371

apps/web/src/app/(landing)/blog/why-the-world-needs-local-first-apps/page.mdx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ In this post, we'll explore why local-first apps are essential, the challenges o
2222

2323
---
2424

25-
### Why local-first apps matter
25+
## Why local-first apps matter
2626

2727
**Local-first apps** shift the power dynamic by ensuring that data is stored and processed primarily on the user's devices. This isn't just a technical distinction; it fundamentally changes the relationship between users and software. Here's why this matters:
2828

@@ -40,7 +40,7 @@ In this post, we'll explore why local-first apps are essential, the challenges o
4040

4141
---
4242

43-
### The trade-offs of client-server architecture
43+
## The trade-offs of client-server architecture
4444

4545
The client-server model has dominated app development for decades, primarily because it simplifies synchronization and backups. However, this convenience comes with significant trade-offs:
4646

@@ -56,29 +56,29 @@ These challenges highlight the need for an alternative approach—one that prior
5656

5757
---
5858

59-
### How Evolu redefines local-first apps
59+
## How Evolu redefines local-first apps
6060

6161
**Evolu** is a library built with the local-first philosophy at its core. It solves the challenges of building local-first apps while retaining the benefits of synchronization and backup through a minimal, optional server.
6262

63-
#### 1. **Full Data Ownership**
63+
### 1. **Full Data Ownership**
6464

6565
Evolu stores all app data in a local SQLite database. Unlike caching or partial storage solutions, this ensures that users have complete control over their data at all times.
6666

67-
#### 2. **Built-in Privacy with Encryption**
67+
### 2. **Built-in Privacy with Encryption**
6868

6969
Data privacy is non-negotiable in Evolu. All local data is encrypted by default, ensuring robust security without requiring developers to configure it manually.
7070

71-
#### 3. **Lightweight, Open-Source Server**
71+
### 3. **Lightweight, Open-Source Server**
7272

7373
Evolu offers a simple, open-sourced **Evolu Server** for synchronization. This server is purely a message buffer with no app-specific logic, ensuring that developers aren’t tied to a single provider or platform.
7474

75-
#### 4. **No Vendor Lock-In**
75+
### 4. **No Vendor Lock-In**
7676

7777
Evolu's design allows developers to leverage cloud services without becoming dependent on them. This approach balances flexibility with independence, giving users and developers peace of mind.
7878

7979
---
8080

81-
### What local-first means to Evolu
81+
## What local-first means to Evolu
8282

8383
The term "local-first" has become an umbrella for various strategies. At Evolu, it specifically means:
8484

@@ -90,12 +90,8 @@ By focusing on these principles, Evolu empowers developers to create apps that p
9090

9191
---
9292

93-
### Why the future is local-first
93+
## Why the future is local-first
9494

9595
The internet is not infallible. Servers go down, companies fail, and errors occur. These realities underline the need for software that doesn't rely solely on centralized systems. **Local-first apps** offer a compelling alternative, combining the best of both worlds: the autonomy of local data with the convenience of optional cloud features.
9696

9797
Evolu makes it easier than ever to build local-first apps, providing developers with the tools they need to create resilient, user-centric software. With Evolu, the future of app development looks brighter, more private, and entirely in the hands of users.
98-
99-
---
100-
101-
Ready to take control of your app's data? Explore Evolu and start building the future of local-first apps today.

0 commit comments

Comments
 (0)