Linear m’a plongé dans le local-first rabbit hole
(bytemash.net)- En utilisant Linear, ma vision du développement d’applications web a profondément changé.
- Linear fonctionne en local-first, offrant une expérience fluide et réactive avec une interaction immédiate sans latence réseau.
- Cette approche donne à chaque client une base de données indépendante et synchronise les changements avec le serveur de manière asynchrone.
- Toutefois, la mise en œuvre est complexe, notamment pour la synchronisation distribuée, la résolution de conflits et la gestion hors ligne.
- Des solutions variées de l’écosystème local-first comme Jazz, Electric SQL et Zero apparaissent, avec une amélioration progressive de l’expérience développeur.
Aperçu
Après avoir utilisé Linear, un outil de gestion de projets, j’ai été fortement inspiré par la vitesse et l’expérience utilisateur remarquables de l’approche local-first. Ce qui était frappant, c’est de ne plus sentir des problèmes courants d’applications web comme la latence réseau, les états de chargement ou les rafraîchissements de page. Grâce à cette expérience, j’ai plongé en profondeur dans les principes techniques et les cas d’application concrets du paradigme local-first.
La plongée dans le trou du lapin
En analysant les dessous techniques de Linear, j’ai découvert qu’ils utilisent l’IndexedDB du navigateur comme une véritable base de données. Toutes les modifications sont d’abord traitées immédiatement en local, puis synchronisées en arrière-plan via GraphQL et Websockets.
- Le terme local-first peut être compris de différentes manières : comme une stratégie UX (réactivité immédiate) ou comme une philosophie consistant à conserver et synchroniser les données localement.
- Dans les applications web traditionnelles, le serveur était la source unique de vérité, mais dans une architecture local-first, chaque client possède sa propre base de données.
- En rapprochant la base de données de l’utilisateur, la latence réseau est éliminée des interactions utilisateur.
Le défi : ce n’est pas trivial
En essayant d’implémenter directement l’approche de Linear, j’ai compris que la complexité est conséquente.
- Gestion de la transition hors ligne/ligne.
- Résolution des conflits entre clients distribués.
- Synchronisation partielle (conçue pour ne pas télécharger l’intégralité des données).
- Migration de schéma des données en cache.
- Sécurité et contrôle d’accès en environnement distribué.
- Ces domaines demandent une quantité considérable de temps et d’efforts d’ingénierie.
L’écosystème local-first en 2025
Au vu des standards de 2025, plusieurs solutions puissantes émergent dans l’écosystème local-first.
- Electric SQL : moteur de synchronisation basé sur Postgres
- PowerSync : solution orientée entreprise
- Jazz : outil pour construire des applications local-first facilement
- Replicache : solution historique majeure (développement arrêté)
- Zero : nouvelle orientation de l’équipe Replicache
- Triplit : synchronisation basée sur TripleStore
- Instant : mise en avant de l’expérience développeur
- LiveStore : fournit une couche de synchronisation temps réel
Approfondissement : Jazz
Jazz attire l’attention par sa promesse unique : « rendre la création d’applications local-first aussi simple que la mise à jour d’un état ».
Le modèle mental
Jazz introduit une structure de collaboration en temps réel appelée Collaborative Values (CoValues).
- Définir un schéma avec Jazz et Zod : cette définition ne se limite pas à un type simple, mais agit comme un objet actif synchronisé automatiquement.
- Sans route API dédiée, logique requête/réponse ou DTO, il suffit de modifier l’état d’un objet pour que la synchronisation se fasse automatiquement.
Comment Jazz y parvient
L’architecture interne de Jazz se résume à :
- Garantie d’unicité : attribution automatique d’un identifiant unique à chaque donnée.
- Event sourcing : conservation de l’historique des modifications sous forme d’événements, pour améliorer l’efficacité de la synchronisation temps réel.
- Chiffrement de bout en bout : chiffrement des données côté client avant la synchronisation. Le serveur ne voit que des blobs chiffrés.
- Conception des autorisations basée sur les groupes : droits d’accès par groupe au lieu d’ACL traditionnelles, avec une distinction claire de la propriété.
Les compromis
Cette architecture est très productive pour le prototypage et le développement rapide d’interfaces, mais certains aspects doivent être pris en compte.
Le serveur est aveugle
Avec le chiffrement de bout en bout, le serveur ne peut pas lire les données des utilisateurs. Si les données auxquelles le serveur doit absolument avoir accès ne sont pas définies au préalable, cela limite la supervision et la prévention du stockage malveillant.
Le voyage dans le temps est obligatoire
L’event sourcing stocke en permanence tout l’historique des changements. Cela rend l’Undo/Redo très pratique, mais la suppression peut devenir difficile à implémenter lorsque l’on doit respecter des contraintes légales telles que le GDPR.
Le stockage prend de l’ampleur
Comme les suppressions ne sont pas appliquées, l’empreinte mémoire augmente progressivement. Cela convient aux petits projets, mais dans un SaaS à grande échelle, le coût de stockage peut grimper fortement.
Le développement local a encore des limites
L’authentification repose sur les Passkeys par défaut ; en auto-développement ou en local, on rencontre encore quelques frictions, notamment pour HTTPS, la gestion des certificats et la migration des clés. Une amélioration est toutefois prévue avec notamment l’intégration de Better Auth.
Mais honnêtement ? Cela vaut le coup
Malgré ces contraintes, l’expérience développeur et la productivité de Jazz restent très impressionnantes. Bien que la solution soit encore à ses débuts, de plus en plus de problèmes devraient se résorber progressivement.
Exploration : Electric SQL et Zero
Contrairement à Jazz, Electric SQL et Zero adoptent une approche incrémentale.
- Il est possible d’utiliser les tables Postgres existantes telles quelles.
- Electric SQL permet de synchroniser l’UI en s’abonnant à une partie d’une table via une reactive query (Shape).
- La manière de gérer les mutations diffère de celle de Jazz, avec des options variées, notamment l’intégration avec LiveStore.
- Zero ressemble à Electric, mais dispose d’un support intégré pour la synchronisation des changements.
Quand le local-first est-il pertinent ?
L’auteur résume ci-dessous les situations où le paradigme local-first est adapté et celles où il est plus difficile à appliquer.
Adapté :
- Outils de création (design, écriture, musique, etc.)
- Applications collaboratives
- Applications mobiles nécessitant un mode hors ligne
- Outils pour développeurs
- Applications de productivité personnelle
Défiant :
- Logique métier serveur à grande échelle
- Exigences strictes de contrôle d’audit
- Systèmes d’analyse de gros volumes
- Systèmes profondément intégrés
- Systèmes qui refusent fréquemment les requêtes côté serveur
Perspectives
Le local-first représente un véritable changement de paradigme dans le développement web. Linear a déjà prouvé un avantage utilisateur significatif. Le développeur doit déterminer si ces compromis structurels conviennent à son projet.
En créant une application personnelle avec Jazz, l’auteur est en train d’expérimenter directement les vrais avantages et les limites de l’abstraction. L’écosystème en est encore au stade initial, et les outils ainsi que les patterns devraient se renforcer avec le temps. Néanmoins, les avantages de conserver les données localement sont clairs et ne devraient pas disparaître.
Si l’on peut accepter les contraintes dans un nouveau projet, il y a suffisamment de valeur à tester le local-first. Dans le pire des cas, on se forme à un nouveau pattern d’architecture ; dans le meilleur, on peut offrir une expérience utilisateur quasi impossible à ralentir. Dans la course à la réactivité autour des 300 ms, c’est un avantage majeur.
1 commentaires
Avis de Hacker News
Zero fournit aussi des fonctionnalités proches d'Electric et prend en charge directement les mutations. Son principal avantage est une synchronisation centrée sur les requêtes. Vous pouvez concevoir l’application par requête, sans décider à l’avance quoi synchroniser ni le configurer : exécuter la requête suffit pour synchroniser autant de données que nécessaire, ou moins. Si le client n’a pas les données nécessaires, la requête bascule automatiquement vers le serveur, et une fois ces données synchronisées elles sont disponibles immédiatement pour les requêtes suivantes. Cette approche est assez pragmatique.
Je suis partisan du SSR. Je pense qu’un client ne devrait avoir en état que le token de session, l’URL actuelle et le DOM. Le réseau et les serveurs continuent de s’améliorer. La vitesse de la lumière est fixe, mais on n’en exploite pas encore tout le potentiel. Des technologies comme le hollow core fiber sont aussi prévues pour réduire la latence globale d’internet d’environ 30 %. Même avec un RTT de 500 ms, une page rendue en SSR en 16 ms paraîtra bien plus immédiate que la plupart des sites web actuels. Je pense qu’il n’est pas raisonnable qu’un serveur rende une réponse HTML pendant plus longtemps qu’une frame à 60 Hz. Avec un cœur Zen 5, on peut sérialiser 30 à 40 Mo de JSON dans ce délai. Du point de vue du serveur, ce n’est qu’une simple chaîne UTF-8 fancy. À mon avis, il faut mesurer ce genre de point en μs, pas en ms. Une latence élevée ne justifie pas d’utiliser le temps CPU de manière paresseuse. Je me suis libéré de la “prison des millisecondes” en utilisant SQLite. Les services SQL hébergés deviennent des entraves quand on vise une latence inférieure à 1 ms. Les standards du navigateur peuvent aussi résoudre une partie des délais de navigation. Voir l’API Speculation Rules.
Le fait que le réseau et les serveurs deviennent plus rapides ne constitue pas une justification universelle du SSR. Cela revient à dire qu’il existe des cas d’exception où le SSR est nécessaire, par exemple parce qu’un serveur possède plus de puissance de calcul ou parce que la séparation de la logique est difficile. En pratique, le rendu côté client est souvent plus rapide. Souvent la logique de rendu est bien plus complexe que la taille des données, et si l’on additionne toutes les ressources côté client, elle peut être plus élevée que côté serveur. Si le SSR avait été l’unique option, l’engouement actuel pour Web Assembly n’aurait pas de sens. L’article sur la local computation vaut aussi le détour. Finalement, on ne peut pas savoir à l’avance quelle approche est la meilleure ; il faut pouvoir choisir au moment de la requête.
Right Tool For The Right Job ! Par exemple, peut-on vraiment faire des outils collaboratifs comme Google Docs ou Google Sheets uniquement en SSR ? Si l’on s’accroche au SSR, on se retrouve dans des situations où une autre personne peut modifier le contenu pendant l’édition en cours. Ces outils fonctionnent bien car l’impact des changements est faible, et parce que les utilisateurs détectent immédiatement qu’ils collaborent ensemble et reçoivent des mises à jour en temps réel. Par contre, un service de réservation d’hôtel n’a pas besoin de synchronisation temps réel. De même, quand la synchro dérive en cours de route ou qu’une app entreprise est conçue sans assez d’anticipation, garantir la cohérence devient assez difficile à cause de la complexité du domaine et du système.
Si on peut ajouter facilement à une app SSR des bibliothèques du type suivant, je les utiliserai :
C’est carrément du happy-path engineering. En dehors d’un happy-path, la réalité peut être assez frustrante.
Le principal usage actuel et futur du SSR, c’est le chargement de la première page, notamment mobile. Ensuite il ne reste que des mises à jour d’état côté client, donc tout doit être plus rapide. Sans qualité d’ingénierie, le SSR ne sert pas à grand-chose.
J’ai développé un gestionnaire de tâches open source en architecture local-first basée CRDT. La motivation était que je n’avais pas besoin de collaboration, et que des outils comme Linear étaient trop complexes pour mon usage. Les avantages de cette architecture sont :
J’ai été très impressionné par Jazz. Le DX est excellent, avec une écriture quasi synchrone et impérative, donc le dev est agréable. Tout parait immédiat et la gestion hors ligne est possible, donc l’UX aussi. Le plus grand problème est le déploiement et la maintenabilité à long terme, notamment la croissance continue des données (je m’en soucie peu puisque la plupart des clients n’ont pas besoin de tout voir). L’enjeu principal est surtout le manque de bonne solution pour les index publics qui changent souvent. En théorie, une liste lisible d’IDs publics est possible ; récemment, en discutant avec Anselm, j’ai compris que c’était en train d’être résolu. Dans l’ensemble, le local-first a un coût non négligeable, mais si Jazz résout les défauts majeurs de l’approche client-serveur traditionnelle, il pourrait remplacer Firestore sur presque tous les aspects.
ElectricSQL et TanStack DB sont excellents tous les deux, mais je me demande pourquoi on s’accroche autant au local-first sur le web. Je pense que mobile en a bien plus besoin à cause du mode hors ligne. Sur web, la connexion est en général présente. Ces technologies local-first paraissent cohérentes en théorie, mais sans résolution de conflits elles tombent vite en rade. C’est un apprentissage tiré de la création d’apps local-first mobiles, et donc pourquoi j’ai choisi les CRDT.
Construire des apps local-first avec les technologies web est infiniment plus difficile que sur native. Une app native permet le hors ligne dès l’installation. Sur le web, il faut bricoler hors ligne avec AppManifest, ServiceWorker, etc. En native, avec du code C de 30 ans, on lit/écrit librement des fichiers sur disque, point final. Sur web, IndexedDB est un vrai cauchemar, localStorage devient inutilisable quand la taille augmente, et OriginPrivateFileSystem est limité. Si un utilisateur ne visite pas le site au moins une fois par mois, Safari supprime l’état local du navigateur. Même avec des tentatives en JavaScript ou build wasm via Emscripten, il faut contourner les APIs web async pour s’en sortir. Apple propose depuis 2015 une solution complète local+sync avec CoreData + CloudKit. Côté Google, Firebase est probablement ce qui s’en rapproche le plus.
En tant que dev de Replicache et Zero, j’ai beaucoup réfléchi à la question de l’obsession du web. Je ne connais pas encore la réponse, mais voici mon avis :
Le mobile a des capacités hors ligne natives bien plus fortes que le web. Mais la vraie productivité / collaboration se passe surtout sur le web, qui est un environnement beaucoup plus hostile. Il y a la synchronisation d’état entre onglets, la suppression de stockage, etc. C’est donc pour cela que le local-first évolue principalement côté web.
Que les moteurs de synchronisation ciblent d’abord le web s’explique surtout parce que ces technologies sont encore jeunes et qu’il y a beaucoup d’updates. Les mises à jour d’une app mobile sont vraiment pénibles, alors que celles d’une web app sont bien plus faciles. Je trouve aussi que les capacités PWA sont désormais correctes. Les PWA iOS supportent aussi les notifications push ; pour moi, une web app au look mobile est déjà largement suffisante.
Le point ici n’est pas “quelles fonctionnalités sont disponibles”, mais le “plaisir” ressenti avec un produit incroyablement rapide et réactif. C’est la raison pour laquelle je vise le local-first.
L’aspect collaboration des moteurs local-first / sync me paraît peu traité. J’utilise Zero pour créer un projet qui remplace le Google Sheet business d’un ami. Nous ouvrons la feuille en direct dans Google Meet pour manipuler les données. C’était une expérience que je n’aurais jamais pensé possible en web app auparavant. J’ai déjà fait beaucoup d’UI live avec WebSocket, mais placer correctement les données entrantes et les gérer au bon endroit est extrêmement complexe. Si la feuille contient des centaines ou des milliers de cellules, le chaos est indescriptible. Avec Zero, on choisit les données par requête et on les modifie avec un mutator : tout le monde est synchronisé immédiatement de façon identique. C’est ce qui rend le dev vraiment agréable.
Quand Google Docs est sorti (j’avais 12 ans), voir la synchronisation temps réel et les curseurs collaboratifs m’a fait penser que l’expérience web allait devenir entièrement collaborative. A l’époque, le cloud computing était un mot à la mode, et même si j’avais tort, je croyais aussi que la collaboration temps réel en était la définition. Mais ce futur-là ne s’est pas réalisé. Vingt ans plus tard, la plupart des produits web sont encore des expériences CRUD (ce site aussi). J’ai eu maintes fois le sentiment que l’innovation était imminente ; on attendait Meteor.js, React, etc., mais ce ne sont pas encore devenus la norme. Je continue à espérer que cela devienne enfin du mainstream, j’ai l’impression qu’un jour ce sera le cas. C’est simplement plus difficile que ce qu’on imagine, et les outillages ne sont pas assez “bon marché” pour être utilisés partout.
Parmi les exemples de collaboration temps réel, il y a Discord (fondamentalement pas très différent de l’IRC des années 90) et Zoom (toutes les visios). Le fait que HN soit une app CRUD est un avantage. Les updates temps réel peuvent au contraire être distrayantes et pas souhaitées.
La vitesse de la lumière est une vraie limite. La lumière met 56 ms pour faire la moitié du tour de la Terre, le signal réel suit encore plus de détours, et les conversions / barrières supplémentaires (load balancer, protection DDOS, etc.) le ralentissent davantage. La latence est souvent plus mauvaise qu’avant.
J’ai aussi ressenti la magie initiale de cette immédiateté, mais j’ai l’impression que ce futur est encore là, quelque part. Malgré tout, je construis un outil de messagerie temps réel façon Google Docs (sans champ de saisie ni bouton d’envoi) : kraa.io/hackernews
Je crois en l’avenir du local-first et des sync-engines. Le tableau des différents frameworks local-first est vraiment utile. Personnellement, Triplit.dev me plaît le plus, et il peut aussi être combiné avec TanStack DB. PowerSync et NextGraph sont aussi des options que je souhaite explorer. Je regarde aussi avec intérêt une présentation sur NextGraph dans une récente vidéo LocalFirst Conf : vidéo YouTube
Je me demande quel support de migration de base de données ces outils offrent. À long terme, lorsqu’il faut supporter des clients sans accès au serveur, ne risque-t-on pas d’avoir un obstacle majeur pour faire une migration en avant depuis un schéma très ancien ? Débuguer le frontend d’un utilisateur unique est déjà difficile ; si en plus il faut gérer l’état du schéma, la complexité peut être au-delà de ce qu’on imagine.
Ça me rappelle Meteor.
Ces approches sont aussi des approches du passé.
Je vais absolument tester Triplit ; je me demande aussi si vous avez déjà essayé InstantDB. J’étais curieux, mais je n’ai pas encore eu l’occasion de le faire.
J’ai beaucoup apprécié l’approche d’Electric, que je suis de près depuis longtemps. Electric est propre car la complexité d’écriture est laissée au développeur et à l’API. Les solutions de synchronisation bidirectionnelle fonctionnent surtout bien pour des apps Todo basiques. Quand on ajoute permissions, évolution de la logique métier, migrations, croissance du produit, la robustesse devient discutable. Electric supporte uniquement la synchronisation en lecture dans la vue, et délègue l’écriture aux API / REST / RPC existantes, ce qui rend l’intégration dans un projet existant plus facile.
Je me demande pourquoi couchdb / pouchdb ne figurent pas dans la liste. Ce sont encore des outils qui fonctionnent assez bien.