MyRoadTrip — Synchronisation offline avec CRDT et Yjs en Vue.js
Comment j'ai construit un planificateur de road trips collaboratif en offline-first avec Yjs, un monorepo Vue.js/TypeScript, Firebase, Mapbox, et Capacitor pour le mobile.
Cet article décortique le projetMyRoadTripLe problème
Alice est dans l'avion, en mode avion. Bob est en 3G dans un train. Les deux modifient le même road trip. Alice change le nom d'une étape. Bob change sa durée. Quand ils se reconnectent — que se passe-t-il ?
Avec un système classique : conflit. On écrase les modifications d'Alice, ou celles de Bob. Ou pire, on demande à l'utilisateur de "résoudre le conflit" — une UX catastrophique.
MyRoadTrip résout ce problème avec Yjs, une implémentation de CRDT (Conflict-free Replicated Data Types).
Pourquoi Yjs ?
Yjs est une bibliothèque JavaScript qui implémente les CRDT — des structures de données mathématiquement garanties de converger vers le même résultat, quel que soit l'ordre des opérations.
La clé : la granularité au niveau du champ. Si Alice et Bob modifient des champs différents du même objet, les deux passent automatiquement. Si par malchance ils modifient le même champ au même moment, le dernier timestamp gagne — un cas extrêmement rare en pratique.
Yjs est intégré côté API (serveur) et côté Web (client), chacun avec sa version :
apps/api:yjs@^13.6.28apps/web:yjs@^13.6.27
Architecture monorepo
MyRoadTrip est structuré en monorepo avec workspaces :
my-roadtrip/
├── apps/
│ ├── api/ ← Backend (Firebase Functions, Yjs serveur)
│ └── web/ ← Frontend (Vue.js, Mapbox, Yjs client)
├── packages/
│ └── shared/ ← Types et logique partagés
├── android/ ← Capacitor (app mobile)
└── firebase/ ← Config Firebase (Firestore, Auth)
Le frontend est en Vue.js avec TypeScript, le backend sur Firebase (Firestore pour la persistance, Functions pour la logique serveur). Le monorepo avec pnpm-workspace.yaml permet de partager les types et la logique métier entre le client et le serveur.
Sync adaptatif
Sur mobile, le polling agressif tue la batterie. Le système ajuste automatiquement la fréquence de synchronisation :
| Contexte | Intervalle |
|---|---|
| Édition active | 2 secondes |
| Application au premier plan, idle | 10 secondes |
| Application en arrière-plan | 60 secondes |
| Batterie < 20% | ×2 |
| Réseau lent (> 500ms RTT) | ×3 |
Pourquoi c'est important ? Parce qu'un road trip se planifie en road trip. Le téléphone est branché sur l'allume-cigare avec 15% de batterie et une connexion 3G instable. L'application doit s'adapter.
Mobile natif avec Capacitor
Le même code Vue.js tourne sur le web et sur Android via Capacitor. La config capacitor.config.ts configure le pont natif. L'app mobile accède à la géolocalisation native, aux notifications push, et fonctionne hors-ligne grâce aux CRDT stockés localement.
Cartographie avec Mapbox
L'interface principale est une carte interactive Mapbox qui affiche l'itinéraire, les étapes, et les points d'intérêt. Les marqueurs sont draggable pour réorganiser le parcours. La collaboration temps réel permet de voir les curseurs des autres utilisateurs sur la carte.
201 fichiers de tests
L'architecture offline et la synchronisation CRDT sont critiques. Pour garantir leur fiabilité :
- Tests unitaires avec Vitest
- Tests E2E avec Playwright
- Tests d'intégration pour les scénarios multi-utilisateurs avec délais réseau simulés
201 fichiers de tests couvrent les cas nominaux, les cas limites, et les scénarios de partition réseau.
Ce que j'en retiens
-
Le CRDT au niveau champ élimine les conflits en pratique. Les cas restants (même champ, même moment) sont si rares qu'ils ne méritent pas une UX de résolution.
-
Le monorepo sauve des vies. Partager les types entre client et serveur évite une classe entière de bugs de sérialisation.
-
Vue.js + Capacitor = web + mobile avec le même code. Le surcoût de Capacitor est minime comparé à maintenir deux codebases.
Florian Sola
Lead Technique · Haute performance temps réel · 9 ans d'expérience