Créer des notes collaboratives avec Yjs et les CRDT

Créer une application de notes collaboratives avec CRDT et Yjs consiste à faire de chaque navigateur une réplique autonome du carnet. Chaque modification est fusionnée sans verrou central, ce qui laisse aux utilisateurs le temps d’écrire, de se déplacer ou de revenir après une coupure réseau.

Le bon niveau de sophistication commence toutefois par un modèle de données simple, un éditeur compatible et un protocole de transport explicite. Yjs fournit le moteur de cohérence, tandis que l’application doit organiser les permissions, la persistance et les interactions entre utilisateurs.

Comprendre la convergence des données

Yjs implémente des types de données répliquées convergentes, ou CRDT. Une note peut utiliser un Y.Map pour ses métadonnées, un Y.Text pour son titre et un autre Y.Text pour son contenu. Deux personnes modifiant le même paragraphe finissent par obtenir le même document, sans qu’un serveur ait à ordonner chacune de leurs frappes.

Chaque changement devient une mise à jour binaire légère. Les vecteurs d’état permettent de demander uniquement les informations manquantes, tandis que Y.applyUpdate fusionne les réceptions locales et distantes. Les curseurs et la présence des utilisateurs sont transmis séparément via Awareness, qui reste volontairement éphémère et ne doit pas servir à stocker des données métier.

Modéliser les notes et répartir le traitement

Une note doit_received un identifiant stable, un titre collaboratif, un corps éditable, des tags et une date de mise à jour. Cette structure permet de déplacer une note entre dossiers sans dupliquer son contenu et de synchroniser chaque modification indépendamment des autres.

const notes = doc.getMap('notes')
const note = new Y.Map()
const titre = new Y.Text()
const corps = new Y.Text()

titre.insert(0, 'Réunion produit')
corps.insert(0, 'Première version')
note.set('titre', titre)
note.set('corps', corps)
notes.set('note-42', note)

Les calculs lourds doivent rester isolés du traitement des frappes. L’indexation, le calcul d’embeddings ou la génération d’aperçus peuvent être confiés au Worker en Node.js, qui réserve un thread dédié sans perturber la boucle d’événements du serveur.

Relier l’éditeur au document partagé

Le navigateur combine généralement un fournisseur WebSocket pour la synchronisation multi-utilisateur avec IndexedDB pour conserver les modifications hors ligne. Ces deux mécanismes sont indépendants : le premier échange les changements avec le serveur, tandis que le second permet de rouvrir une note sans connexion.

const doc = new Y.Doc()
const local = new IndexeddbPersistence('carnet-42', doc)
const distant = new WebsocketProvider(
  'wss://notes.exemple.fr',
  'carnet-42',
  doc
)

Un éditeur comme CodeMirror, Monaco ou TipTap reçoit ensuite le Y.Text correspondant et applique ses changements au document partagé. Les sélectionnels et les avatars peuvent être publishés avec Awareness. À la destruction du composant, les écouteurs doivent être retirés et les fournisseurs fermés afin d’éviter les connexions fantômes et les fuites de mémoire.

Organiser la collaboration et les rôles

La synchronisation serveur utilise une salle logique par carnet, éventuellement associée à un tenant et à un niveau d’accès. Le serveur authentifie la connexion, vérifie ces droits puis relaie les mises à jour. Une modification concurrente n’est pas rejetée : elle est fusionnée de manière déterministe, ce qui élimine la plupart des conflits d’interface.

La gestion des rôles reste toutefois indispensable. Lesparticipants peuvent éditer, commenter ou simplement consulter, selon les règles définies par l’équipe. L’adoption de pratiques agiles facilite ces-itssplits fonctionnels courts, les critères d’acceptation fondés sur la convergence et les livraisons progressives avec des drapeaux de fonctionnalité.

Persister les données et sécuriser le déploiement

La persistance locale sert au mode hors ligne, pas à la sauvegarde durable. Côté serveur, les mises à jour Yjs peuvent être ajoutées à une base transactionnelle ou à un stockage objet, puis regroupées périodiquement dans un instantané compact. Une procédure de restauration doit être testée avec des mises à jour réellement manquantes ou corrompues.

Les solutions cloud simplifient le déploiement du service WebSocket, le stockage des instantanés et la supervision. Elles ne remplacent pas les mesures de sécurité :TLS chiffre les connexions, les données au repos restent chiffrées, les jetons expirent et chaque requête vérifie l’appartenance au carnet. L’isolation des tenants doit être testée avant toute mise en production.

Enrichir la recherche et l’indexation

Une recherche lexicale sur les titres, les tags et le corps des notes apporte déjà une expérience satisfaisante. Pour les carnets volumineux, un index inversé ou une base de recherche dédiée réduit le délai de réponse. L’index doit être reconstruit à partir des 更新 Yjs, sans devenir la source de vérité.

Une recherche sémantique peut proposer des notes proches à partir de leurs embeddings. L’algorithme des plus proches voisins compare les vecteurs et sélectionne les résultats les plus proches du texte recherché. Cette étape reste optionnelle : son calcul s’exécute en arrière-plan, ses résultats sont liés par l’identifiant de la note et chaque index possède sa propre version.

Repères de validation et mise en action

Une recette solide combine tests automatisés, scénarios de coupure et observation des performances. Les listes suivantes constituent un point de départ léger pour sécuriser le comportement collaboratif.

Tests à automatiser

Contrôles de production

Après les recette, l’équipe peut prévoir de courtes périodes de récupération. Une ressource de récréation en ligne offre une coupure voluntary loin de l’écran, sans empiéter sur les phases de validation.

Lancez dès aujourd’hui un prototype avec un Y.Doc, un éditeur collaboratif et une persistance IndexedDB. Ajoutez ensuite le serveur WebSocket, les tests de convergence et la recherche, afin de transformer progressivement le carnet partagé en une application fiable.