Developers are increasingly getting excited about local-first as the app architecture of the future. [1] [2]
In the local-first paradigm, app code works directly with a client-side embedded database, which optionally automatically syncs with a backend database in the background. This is in contrast to cloud-first apps which primarily use a cloud datastore via APIs.
Working with a local database (e.g. SQLite) means that apps feel instant to use because of low latency, and are functional even if the user’s network connection is unreliable or offline. In addition, local-first typically offers built-in multi-user real-time collaboration by automatically syncing data in the background bi-directionally.
The application architecture of the future
While some apps will always necessarily have to rely only on synchronous network communication with a server (e.g. banking/payments, video streaming, etc.), it is likely that local-first will become the default architecture for the majority of apps in the future, because of its substantial benefits for developers (simplifying distributed state management) and end-users (a delightful user experience).
Local-first has gradually been gaining popularity for mobile apps, but with SQLite in the browser using Wasm becoming more robust (combined with using service workers to pre-cache assets/resources so that web apps can load offline), it’s something that will increasingly revolutionize web apps too. Once local-first architecture becomes mainstream on the web, the impact of this architectural shift will be much bigger.
From differentiator to table stakes
Apps like Figma, Linear and Superhuman are widely admired for their stellar user experience powered by a local-first or similar architecture under the hood. Importantly, they’ve used it as a differentiator to disrupt their respective markets and wrest customers from incumbent competitors.
As local-first gains more momentum, the features/benefits that it provides to users (instant responsiveness, network resilience, offline use and real-time collaboration) could easily become “table stakes” rather than differentiators.
Here be dragons?
Many developers may instinctively shy away from local-first (and previously offline-first [1]) and stick to cloud-first apps, because it’s pretty complex and painful to build a robust active-active sync engine (challenges are numerous and include dynamic partial replication, conflict resolution, consistency guarantees, and more. Add in the need to support web apps and the complexity escalates further).
That is not to say that cloud-first apps are without complexity traps either: In an API-powered app front-end, caching and application state management can quickly get hairy too.
Fortunately, drop-in sync engines like PowerSync are now starting to make local-first architecture even simpler to build than cloud-first apps. We’re excited to be part of this movement and our aim is to help accelerate the adoption of this architecture, especially since announcing support for web.
Deeper dive: Why local-first matters
Lower backend compute load, dependency and cost
- In a typical cloud-first app, clients are constantly hitting the backend with API requests to read or write data. The backend becomes a performance bottleneck.
- Local-first apps move the vast majority of read operations to the client-side, using the local embedded database. Because all the relevant data is right there in the local database, many computations can also be done locally. This reduces backend complexity, load and scale-out needs — thereby reducing cloud computing costs. [3] In addition, because these apps are not reliant on the network and backend at all times, backend uptime and performance is much less of a factor in app performance.
Simplified backend development: Reduced API burden
- In a typical cloud-first app, every added feature results in a need for new or expanded backend APIs. Developers often have to invest significant effort in implementing and maintaining API functions.
- An offline-first architecture drastically reduces the API development burden: By replicating the backend database to the client-side proactively, we already have all the data we need right there in the client without having to consult a backend API. Quoting Tuomas Artman of Linear: “We don’t have to make REST calls, we don’t have to make GraphQL calls. We modify the data, we save it, and everything always updates.” Dynamic partial replication is a key piece of functionality here, since we only want to replicate the part of the backend database that is relevant to the specific user.
Simplified state management, simplified app codebase
- Many developers have learned the hard way that distributed state management can be very complex and painful. As mentioned above, this is no less true for cloud-first apps, where developers typically respond to app performance issues by implementing some variation of caching and optimistic updates. This can very quickly lead to spiralling complexity around cache invalidation and inconsistent states.
- In local-first apps, the global state is simply stored in the local database. This substantially simplifies application code since queries can be done synchronously and the developer does not have to handle loading states or network failure states. On the web, the benefits go even further: State can be shared across multiple browser tabs on the same user’s device, all accessing the same local database. By utilizing an active-active replication sync engine working in the background, the developer also doesn’t have to think about state transfer.
At Meta, once the Project Lightspeed team migrated their Messenger app to a SQLite-centric architecture, the benefits were clear: “We leveraged the SQLite database as a universal system to support all the features. [...] The UI merely reflects the tables in the database.”
Ink & Switch says about the benefits of local-first: “[It] allows app developers to focus on their application rather than the challenges of data distribution”. This is echoed by Tuomas Artman of Linear: “Once we’ve put in the effort on putting the synchronization engine in, our task of developing this application became a lot easier and a lot faster.”
To the user, everything feels instant. No loading spinners.
- In a cloud-first app that relies on APIs (REST, GraphQL), most data operations require a round-trip to a server. This always results in some latency — typically requiring the user to stare at a loading spinner.
- By contrast, a local-first app that uses a local embedded database in the application front-end (with a sync engine working in the background) dramatically cuts down on the latency of reading and writing data, which means that the app is much faster for users — the UI can react near-instantaneously to user input. [4]
Offline use / network resilience / high availability
- In a cloud-first app, the user is at the mercy of the network — even a momentary loss of mobile network coverage can result in the app failing to load or save data.
- In a local-first app, the network is no longer on the critical path for user interactions — meaning that the app is resilient to any interruptions in network service quality, or even complete downtime of the network connection or backend. Users can use the app while offline for brief periods of time (e.g. passing through a tunnel) or for extended periods of time (in the subway, in the mountains, in the basement of a building, etc). [5] With conflict resolution and consistency guarantees, the intricacies of combining offline usage with multi-user collaboration can also be managed.
Real-time collaboration and reactivity
- With a typical cloud-first app architecture, the developer has to invest significant effort into building real-time streaming functionality in order to enable collaboration between different users. This may require building or otherwise bringing in a whole new system.
- On the other hand, sync engines such as PowerSync used for local-first apps come built-in with key components that naturally enable real-time multi-user collaboration, since they are fundamentally designed to monitor for data updates and keep users in sync. [6]
Designing for local-first
A local-first architecture will result in much of your app functionality living on the client-side rather than on the backend. [7] Much of your business logic, querying, filtering and computation will happen client-side.
That being said, for security reasons you still want to enforce certain business logic, authorization and validation on the backend. In this sense, PowerSync’s architecture is beneficial because it sends writes through your backend, putting you as the developer in control of the write process. This protects your backend database as the source of truth / system of record. And because PowerSync uses server reconciliation for consistency, your backend has the ability to reject writes based on your business logic / authorization / validation, and this will be kept consistent across clients.
What about Firebase offline support?
A common misconception is that Google Firebase’s Cloud Firestore provides a local-first or offline-first architecture. It’s more accurate to describe Cloud Firestore as a cloud-first system: By default, queries run against the cloud (online) datastore and the results of those specific queries are cached on the local device (and may be removed from the cache later). Cloud Firestore does not preemptively sync a database to the local user device for offline-by-default access.
In addition, by default Cloud Firestore will try to reach the server first before falling back to the local cache, which can result in frustration and significant latency for the user if their network connection is patchy.
Ushering in the local-first future
Hopefully it’s clear from the above that local-first architecture will increasingly be a game-changer for both developers and end-users. And by making this architecture easier to implement for web apps, the total potential impact on the software world is expanded enormously.
Questions? Comments? Join us on Discord
If you have any thoughts to share or questions, please join us on our Discord server.