Azure regions are coming to Neon very soon. Sign up for the waitlist to get early access
Community

Comparing local-first frameworks and approaches

Plus an alternative: Neon branches + CLI

Post image

Any sufficiently advanced technology is indistinguishable from magic.

Arthur C. Clarke

Cloud applications feel magical—you edit a document on your laptop, and it updates everywhere instantly. Platforms like Replit enable entire teams to code together in real time from anywhere. But like magic shows, a lot is happening under the hood that we’re not supposed to see. Relying always on the cloud can raise issues around connectivity, data ownership, privacy, and security.

Working locally offers isolation and greater control over your environment, and local-first frameworks are indeed making a comeback. In this post, we’ll explore some of them, and we’ll also discuss how Neon offers many of the advantages of local-first via branches and the Neon CLI—making it very, very simple to build and test in isolation.

CRDTs and Automerge 

In 2019, Martin Kleppmann and colleagues from Ink & Switch published the manifesto Local-first software: you own your data, in spite of the cloud. They argued that cloud-first models make users “borrowers” of their own data, leading to issues like lack of offline usability, loss of data ownership, and dependency on service providers.

To address these problems, they proposed seven ideals for local-first software:

  • No spinners: Fast performance using local data.
  • Multi-device: Seamless sync across user devices.
  • Offline: Full functionality without internet access.
  • Collaboration: Real-time collaborative editing.
  • Longevity: Data access and editability for decades.
  • Privacy: Encrypted data inaccessible to providers.
  • User control: Ownership to copy, modify, and delete data.

A key concept to achieve these aims is the Conflict-Free Replicated Data Type (CRDT). CRDTs allow multiple replicas of data to be updated independently and concurrently, ensuring all replicas eventually converge to the same state without conflicts—ideal for local-first architectures.

For example, in a collaborative text editor, CRDTs enable users to work offline and merge changes seamlessly when reconnected. Ink & Switch released Automerge, a library that uses CRDTs to handle merging automatically:

import { next as Automerge } from "@automerge/automerge"

let doc = Automerge.from({text: "hello world"})

// Fork the doc and make a change
let forked = Automerge.clone(doc)
forked = Automerge.change(forked, d => {
    // Insert ' wonderful' at index 5, don't delete anything
    Automerge.splice(d, ["text"], 5, 0, " wonderful")
})

// Make a concurrent change on the original document
doc = Automerge.change(doc, d => {
    // Insert at the start, delete 5 characters (the "hello")
    Automerge.splice(d, ["text"], 0, 5, "Greetings")
})

// Merge the changes
doc = Automerge.merge(doc, forked)

console.log(doc.text) // "Greetings wonderful world"

This approach enables true local-first collaboration, where users can work offline and sync their changes when they reconnect, without the need for a central server to arbitrate conflicts.

PouchDB

PouchDB is a JavaScript database that runs in the browser, allowing developers to create applications that work offline and sync with server-side databases when online. It’s designed to be compatible with (and is inspired by) Apache’s NoSQL CouchDB.

The core principles of PouchDB align well with the local-first manifesto:

  • Offline-first. Works entirely offline, syncing when a connection is available
  • Multi-device sync. Seamlessly syncs data across devices
  • Open source. Allows for community contributions and audits
  • Cross-platform. Runs in browsers, Node.js, and mobile devices

PouchDB uses a document-based data model, where each document is a JSON object. Here’s a simple example of creating and querying a PouchDB database:

// Create a database
var db = new PouchDB('my_database');

// Add a document
db.put({
  _id: 'mittens',
  name: 'Mittens',
  species: 'cat',
  age: 3
}).then(function () {
  // Find all documents where species is 'cat'
  return db.find({
    selector: {species: 'cat'}
  });
}).then(function (result) {
  console.log(result.docs);
}).catch(function (err) {
  console.log(err);
});

One of PouchDB’s key features is its sync protocol. When online, PouchDB can sync with a CouchDB server, allowing for real-time collaboration:

var localDB = new PouchDB('my_database');
var remoteDB = new PouchDB('http://example.com/my_database');

localDB.sync(remoteDB, {
  live: true,
  retry: true
}).on('change', function (change) {
  console.log('Data changed:', change);
}).on('error', function (err) {
  console.log('Sync error:', err);
});

PouchDB doesn’t use CRDTs for conflict resolution. Instead, it uses a deterministic algorithm based on revision history. When conflicts occur, PouchDB chooses a winning revision and stores the others as conflicts, which can be accessed and resolved manually. If you want to nerd it out, here’s a good read on how CouchDB provides eventual consistency.

While PouchDB provides a robust solution for offline-first apps, it’s not without challenges. Large datasets can impact performance. And like other local-first solutions, developers need to carefully consider data modeling and conflict resolution strategies.

PouchDB shows us that local-first isn’t just about new, bleeding-edge tech–it’s about reimagining existing databases for a world where offline is the norm, not the exception.

Distributing data with dAPPs

While CRDTs and data syncing focus on the online/offline dichotomy, decentralized applications (dApps) take the concept of local-first software a step further by decentralizing the entire application infrastructure. dApps are applications that run on a peer-to-peer network of computers rather than a single computer. They are typically open-source, use blockchain technology, and operate autonomously without a central authority.

The core principles of dApps align closely with the local-first manifesto:

  • Decentralized. No single entity controls the network
  • Deterministic. Same function always produces the same result
  • Turing complete. Can perform any computation or script
  • Isolated. Executes in a virtual environment (like Ethereum Virtual Machine)

Imagine a decentralized social media platform. Users could own their data, control who sees it, and even move it between different front-end clients. Here’s a simplified Solidity smart contract for user profiles:

pragma solidity ^0.8.0;

contract UserProfile {
    struct Profile {
        string name;
        string bio;
        string[] posts;
    }

    mapping(address => Profile) public profiles;

    function updateProfile(string memory _name, string memory _bio) public {
        Profile storage profile = profiles[msg.sender];
        profile.name = _name;
        profile.bio = _bio;
    }

    function addPost(string memory _content) public {
        profiles[msg.sender].posts.push(_content);
    }
}

All data is stored on the blockchain and the user’s Ethereum address serves as their unique identifier, giving them full control over their data. For more sophisticated use, decentralized databases such as Gun are available. Gun uses CRDTs, but the eventual “online” database is also decentralized. 

dApps move users away from centralized services to a model where they truly own and control their data and interactions. While they face challenges in scalability and user experience, dApps hold promise for creating a more open, resilient, and user-centric internet. As with CRDTs, the magic here isn’t in making your data appear everywhere–it’s in making sure your data stays yours, no matter where it goes.

More local-first frameworks 

We’ve delved into CRDTs, PouchDB, and dApps, but the landscape of local-first frameworks is rich and varied. A few more that are making waves:

  • Yjs: A high-performance CRDT implementation that enables real-time collaboration across platforms. Yjs is modular, allowing developers to pick and choose the features they need, and integrates well with popular frameworks like React and ProseMirror.
  • Realm: An object-oriented database that runs directly on mobile devices. Realm offers real-time synchronization between devices and the cloud, making it ideal for building responsive mobile apps that need to work offline.
  • Couchbase Lite: A mobile version of Couchbase Server designed for offline-first applications. It provides local data storage with sync capabilities, ensuring that apps remain functional without network connectivity.
  • ElectricSQL: A local-first synchronization solution that extends SQLite with real-time sync and collaboration features. ElectricSQL uses SQLite on the client side and syncs with a Postgres database on the server, a combination they refer to as “pglite”.

Bridging local and cloud: Using Neon branches via CLI 

While Neon isn’t a local-first framework per se, it offers features that resonate with the local-first philosophy. Neon’s branch-and-merge workflow provides developers with the best of both worlds: the robustness and scalability of cloud services, along with the isolation and control of local development.

What is Neon?

Neon is a serverless Postgres platform that focuses on enhancing the developer experience for databases. One of its standout features is the ability to create database branches via copy-on-write, including data + schema, offering a similar experience to how you would branch code in Git. This capability allows developers to spin up isolated copies of a database for developing on their own machine.

Alignment with local-first principles

While Neon isn’t a local-first framework in the traditional sense, it incorporates several principles that resonate with the local-first philosophy:

  • Neon’s branching provides the isolation developers seek in local-first environments, allowing for safe experimentation without risking the integrity of the main database. 
  • Neon’s branches aren’t offline-capable by default, but the Neon CLI enables developers to work with database branches locally. You can develop and test your applications even without constant internet access.
  • After making changes in an isolated branch, you can apply them to the main database being rest assured that the changes won’t be breaking. This process handles synchronization and potential consistency issues, similarly to how CRDTs manage data merges in local-first applications.
  • This enables collaborative workflows where multiple developers can work concurrently and merge changes to production seamlessly.

Example use case

Imagine a development team working on a new feature that requires substantial database schema changes: 

  • Each developer creates a branch of the main database using Neon, isolating their changes from the production environment.
  • They use the Neon CLI to work with their database branch locally, allowing for development without constant internet access and providing better performance.
  • Developers test their changes extensively in isolation, ensuring they meet all requirements and won’t introduce issues.
  • Once ready, they incorporate their branches back into the main database. 

Advantages 

  • Ease of use. Creating branches is instantaneous and very easy. The Git-like feel simplifies workflows and is familiar to developers.
  • Security. Neon takes care of security with robust authentication, encryption, and compliance with industry standards.
  • No need to implement complex synchronization logic or conflict resolution strategies.
  • You can scale your team without changing your workflow. Many team members can collaborate on features without conflict. 
  • You keep all of Postgres. 

Conclusion 

Local-first frameworks redefine our relationship with data and application architecture. They’re not just providing solutions to work offline;  they’re making us rethink the entire user-data relationship, empowering us with greater control and privacy so we can build applications faster. Via local branches, Neon has a hybrid approach that blends the strengths of local and cloud environments. You can try the workflow for free by using the Neon free tier.