How local first works

Abstract illustration monochrome
Rūtenis Raila
Rūtenis Raila

Introduction

It’s often said that the architecture you use determines an application’s performance more than the programming language or framework you use.

This sounds true intuitively, but one can’t help but wonder how to apply such statements in real life.

Local first is one such architecture that allows building applications to feel instant to the user. Yes, instant as in the loading time is the next frame of the user’s device screen.

We’re going to explore at a high level how such architecture works and then look at a real-world example. I will also provide a list of technologies that help implement such architecture if you’d like to build something using them.

Video

If you prefer visual explanations, there's also a video version.

How it works

Before we dig deep into how local first architecture works, let’s first have a quick look at how a typical web application works so we can contrast the two.

There are two main parts:

Classic server

This is where your database and the application server are. The majority of your business logic is performed there, and your data solely lives there.

Classic client

This is where your application UI is displayed. It could be a browser or a native app. Little to no data is typically saved there.

Now let’s contrast this with a local first system.

It also has the same parts we’ve just talked about, but they are doing different things:

Local first server

This is where your database and the application server are. It’s the source of truth for your application data.

Some local first systems are also server authoritative, meaning that the server gets to say what the current state of the data is, regardless of what state clients have.

Local first client

This is where your application UI is displayed. The client has a lot more responsibility than in the classic architecture.

As the name implies, the data is stored locally first. It’s usually stored in an embedded SQLite database or the browser’s IndexedDB.

Because the data is local, all the UI interactions feel and are instant.

Real-world example

Here are some code examples from the application I’m working on that uses local-first architecture.

The technology I’m using is Postgres, Drizzle and ZeroSync. But the same principles apply regardless of your stack.

Database schema

This defines the tables that will be created in my Postgres database. It’s the ultimate source of truth for my application.

Sync engine schema

This defines the shape of the data that will be synced to the client. It can be the whole table or a subset of the properties that you want to display to the user.

Sync engine mutator

This is where you can put your business logic. In my case, I’m validating if there’s a team for the task and generating a slug for the task.

I decided to extract it to a function that I can run on both the client and the server, as well as my testing suite. However, in most sync engine systems, you could just run the mutation directly in a React component.

Sync engine query

This is the final piece of the puzzle. You can see how data is fetched in a simple React hook.

Let’s recap how this works in the real world

  • A user submits a form with task details.
  • The mutator runs, updates the IndexedDB instantly.
  • The UI updates instantly reflect to the user that a new task was added.
  • The same mutator is also run on the server. In my case, an AWS Lambda function, but it could be any type of server.
  • In the background, the server verifies that indeed the data was mutated and silently updates the ui via Websockets with the new server data.


From the user's perspective, the UI feels instant.

From the developer’s perspective, the complexity cost is similar to using something like GraphQL, REST API + SPA, tRPC, etc. Yet the application feels instant.

It’s a fair trade if you ask me, I’m willing to eat up the complexity cost for an app that can render the UI updates the next frame.

With this architecture, I don’t have to worry about how fast my server responds as much, because the updates are synced in the background.

Use cases where local first is useful

B2B SaaS, Consumer apps, Internal dashboards.

Apps like Linear, Figma, Superhuman.

Why? Because the data is structured in tenants (organisations, workspaces). You can load a good amount of that data locally.

Also, most interactions happen in the same few pages, so you’re reusing most of that data anyway.

Use cases where local first is not as useful

Marketplaces, e-commerce sites, news sites, and social networks.

Websites like Airbnb, eBay, Bloomberg, Facebook, etc.

Why? Because as a user, you consume a lot more information, so the data loading and storing on your local device will be much more complex. It’s still possible with modern sync engines, but it can put a lot of load on your infrastructure.

Local first technologies I recommend

ZeroSync

Made by the same team as Replicache. Made by the same team as Replicache. API looks similar to React Query.

LiveStore

Made by the original creator of Prisma. A new solution that is inspired by classic frontend patterns, so if you’re familiar with Redux or the Flux pattern, this might be a good fit for you.

Conclusion

Local first is one of the top technologies I’m excited about. I’m just excited about it as AI.

I hope this article inspired you to check these technologies out, because I think they’re super interesting and underrated!

Further reading

DBSP: Automatic Incremental View Maintenance for Rich Query Languages.

  • If you’re a fan of complex computer science problems and want to deeply understand the nuances of local-first systems, you might enjoy this research paper.
  • https://www.vldb.org/pvldb/vol16/p1601-budiu.pdf