Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

16.2 — Temps réel & collaboratif

🎯 Objectif : choisir la bonne brique pour chaque besoin temps réel — chat, collaboration, vidéo. Comprendre quand un WebSocket suffit, quand SSE bat WebSocket, quand il faut un CRDT, et combien ça coûte de scaler.

À l'issue de cet axe, tu sauras :

  • Différencier polling, long-polling, SSE, WebSocket et WebRTC
  • Choisir entre WebSocket maison, Pusher / Ably / Liveblocks
  • Implémenter un éditeur collaboratif avec Yjs et résoudre les conflits
  • Comprendre les CRDT et quand ils sont préférables au server-of-truth
  • Anticiper les coûts et la scalabilité (sticky sessions, fan-out)

Avancé 12 min prérequis : axes 1-14 maîtrisés

HTTP polling → demande périodique simple (gaspille)
HTTP long-polling → connexion ouverte jusqu'à event
SSE (Server-Sent) → flux unidirectionnel server → client
WebSocket → bidirectionnel, full-duplex
WebRTC → P2P, vidéo / audio / data, NAT traversal
WebTransport (HTTP/3) → bidirectionnel, multi-stream, encore jeune en 2026
ProtocoleDirectionQuand l’utiliser
HTTP pollingrequest/responseTrès peu d’updates (1×/min)
Long-pollingclient → server tient la connexionFallback si WS bloqué
SSEserver → client (uni)Notifications, dashboards lecture seule, IA streaming
WebSocketbidirectionnelChat, présence, collab
WebRTCP2P bidirectionnelVidéo / audio / data peer-to-peer
WebTransportmulti-stream HTTP/3Cas de niche, latence ultra-basse

Règle : choisis le plus simple qui fait le job. SSE bat souvent WebSocket pour les flux serveur→client (LLM streaming par exemple).


  • Un endpoint HTTP avec Content-Type: text/event-stream.
  • Le serveur écrit data: ...\n\n à chaque event.
  • Le client se connecte avec EventSource.
  • Reconnect automatique géré par le navigateur.
  • HTTP/2 multiplexe → pas de limite « 6 connexions par origine » des temps anciens.
import { streamSSE } from 'hono/streaming';
app.get('/events', (c) =>
streamSSE(c, async (stream) => {
for await (const evt of subscribeToBus()) {
await stream.writeSSE({ event: evt.type, data: JSON.stringify(evt.payload) });
}
})
);
const es = new EventSource('/events');
es.addEventListener('message', (e) => console.log(JSON.parse(e.data)));
es.addEventListener('order.created', (e) => /* ... */);
es.onerror = () => console.warn('reco auto en cours');
  • Notifications, dashboards live (read-only).
  • LLM streaming (ChatGPT, Claude API renvoient en SSE).
  • Logs en direct.
  • Position d’une commande / livraison.
  • Bidirectionnel (chat, collab) → WebSocket.
  • Binaire pur (jeu, vidéo) → WebSocket binaire ou WebRTC.

  • Handshake HTTP Upgrade: websocket.
  • Connexion persistante, full-duplex, binaire ou texte.
  • Pas de structure : tu décides du protocole applicatif (JSON, MessagePack, Protobuf).
  • Sticky sessions souvent nécessaires côté infra (WebSocket = stateful).
  • Reconnect à coder côté client (la lib s’en charge typiquement).
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ noServer: true });
wss.on('connection', (ws) => {
ws.on('message', (msg) => {
const data = JSON.parse(msg.toString());
if (data.type === 'chat') broadcast(data);
});
});
server.on('upgrade', (req, socket, head) => {
wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));
});
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => ws.send(JSON.stringify({ type: 'auth', token }));
ws.onmessage = (e) => handleMessage(JSON.parse(e.data));
ws.onclose = () => setTimeout(reconnect, backoff());

WebSockets n’ont pas de header Authorization (sauf hack en sub-protocol). Patterns 2026 :

PatternExplication
Cookie sessionSi même origine — WS hérite des cookies
Token dans l’URLwss://api/ws?token=xxx — attention aux logs
Sec-WebSocket-Protocolnew WebSocket(url, ['Bearer.eyJxxx']) — astuce courante
Auth après handshakePremier message = auth, kick si raté en 5s
  • Mémoire : ~10-50 KB / connexion. 100k WS = 5 GB RAM minimum.
  • Sticky sessions : load-balancer doit router vers le même node — Cookie ou IP hash.
  • Fan-out : message à diffuser à 1000 clients = 1000 envois. Redis pub/sub ou NATS souvent nécessaire entre nodes.

Si tu n’as pas envie de gérer ça : Pusher, Ably, PartyKit (Cloudflare Durable Objects), Supabase Realtime.


ServiceForceModèle
Pusher ChannelsMature, simple$/messages
AblyTrès solide, multi-région$/messages, generous free
Supabase RealtimePostgres CDC + presenceInclus avec Supabase
PartyKitCloudflare Durable ObjectsEdge global, par room
LiveblocksSpecialised collab + presence$/MAU
Pusher BeamsPush mobile cross-platform$
CentrifugoOSS self-hostgratuit

Quand un service managé :

  • Équipe < 5 backend qui ne veut pas opérer ça.
  • Tu n’as pas le scale (sub-100k clients concurrents).
  • Tu veux multi-région sans souffrance.

Quand l’auto-héberger :

  • Volume énorme (le coût $/message dépasse l’OPEX).
  • Compliance (données ne doivent pas sortir).
  • Cas très spécialisé.

WebRTC est complexe. À ne mettre en œuvre qu’en sachant ce qu’on fait ou via une lib qui abstrait.

┌────────┐ ┌────────┐
│ Peer A │ ── DataChan ── │ Peer B │
└────┬───┘ └───┬────┘
│ │
└────── STUN / TURN ─────┘
┌────────┐
│ Signal │ (WebSocket, your server)
└────────┘
BriqueRôle
STUNDécouvre l’IP publique derrière NAT
TURNRelai si NAT traversal échoue (~20 % des cas en pratique)
SignalingÉchange initial (SDP, ICE) — pas standardisé, ton choix
DataChannelTunnel binaire / texte P2P
MediaStreamAudio / vidéo capturé via getUserMedia

Très rarement. Sauf :

  • Tu construis un produit niche (jeu temps réel P2P).
  • Tu veux contrôler à 100 %.
OutilQuoi
LiveKitOSS self-host ou cloud, SFU mûre
mediasoupOSS Node-friendly, plus bas niveau
JanusOSS C, pour les fous
Agora.io / Daily.co / Twilio VideoSaaS clé-en-main

Pour 99 % des cas : LiveKit (OSS + cloud) ou Daily.co / Agora (SaaS) — pas de SFU maison.


CRDT — collaboration multi-utilisateur sans conflits

Section intitulée « CRDT — collaboration multi-utilisateur sans conflits »

Deux utilisateurs éditent la même phrase hors-ligne, puis se reconnectent. Comment merger ?

ApprocheSouci
Last-write-winsOn perd les éditions de l’un
LockPas de collab vraie
OT (Operational Transform)Compliqué (Google Docs au début)
CRDTMathématiquement convergent — pas de central authority

Un CRDT (Conflict-free Replicated Data Type) est une structure de données qui :

  • Peut être modifiée simultanément par plusieurs réplicas.
  • Convergence automatique : peu importe l’ordre des opérations, tous arrivent au même état.
  • Fonctionne en P2P ou avec un server relay.
  • Supporte offline-first trivialement.

Yjs est la lib CRDT JavaScript dominante.

import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
const ydoc = new Y.Doc();
const provider = new WebsocketProvider('wss://yserver.example', 'room-1', ydoc);
const ytext = ydoc.getText('content');
ytext.observe((event) => {
console.log('Changement détecté', ytext.toString());
});
// Modification — synchronisée chez tous les peers connectés
ytext.insert(0, 'Hello ');

Types CRDT supportés par Yjs :

Type YjsÉquivalent JS
Y.Textstring
Y.ArrayArray
Y.MapMap
Y.XmlFragmentDOM XML
  • y-prosemirror / y-tiptap — éditeurs riches collaboratifs (style Notion).
  • y-codemirror / y-monaco — éditeurs de code collab (VSCode-like).
  • yjs-tldraw — canvas collaboratif.

Affiche les curseurs des autres participants :

provider.awareness.setLocalStateField('cursor', { x, y });
provider.awareness.on('change', (changes) => {
for (const [clientId, state] of provider.awareness.getStates()) {
if (state.cursor) renderCursor(clientId, state.cursor);
}
});
  • y-websocket-server — démarre en 5 lignes, parfait pour MVP.
  • y-redis — fan-out via Redis pour multi-node.
  • Liveblocks — managed avec presence + persistence.
  • TipTap Hocuspocus — Yjs server avec auth, persistence, hooks.

CRDT en mémoire ne survit pas au redémarrage. Stratégies :

  • y-leveldb ou y-mongodb côté serveur.
  • Snapshot régulier (toutes les N modifs) en DB classique.
  • Liveblocks / Hocuspocus persistent automatiquement.

Automerge — autre CRDT JS, géré par Ink & Switch. Plus axé sur le local-first et les apps offline. Focus différent.

2026 : Yjs domine pour les éditeurs collab live. Automerge brille pour les apps fortement offline-first type Linear.


Client A ──┐
Client B ──┼── WebSocket ── App ──┐
Client C ──┘ │
Redis Pub/Sub ◄── Worker batch

Simple, scalable jusqu’à ~50k clients par node.

Pour multi-node :

LB sticky ──┐
├── App1 ────┐
├── App2 ────┼── Redis ──── stocke état partagé
└── App3 ────┘

Chaque app reçoit ses clients (sticky), Redis fan-out les broadcasts entre apps.

Pattern 3 — Edge realtime (Cloudflare Durable Objects)

Section intitulée « Pattern 3 — Edge realtime (Cloudflare Durable Objects) »

Une room = un Durable Object qui vit à l’edge le plus proche. Coordination, persistance, single-source-of-truth — sans gérer Redis ni sticky sessions. Latence ~50 ms mondialement.

PartyKit / Liveblocks sous le capot.

Pas de serveur applicatif. Idéal pour jeux, doc local. Mais STUN/TURN restent indispensables.


Au-delà des messages, tu veux savoir qui est là, qui tape, où sont les curseurs.

SolutionQuand
Y.awarenessSi tu utilises Yjs (gratuit)
Liveblocks PresenceSi tu utilises Liveblocks
Custom heartbeat WSDIY simple
Phoenix ChannelsSi stack Elixir

Heartbeat custom :

// Client : ping toutes les 10 s
setInterval(() => ws.send(JSON.stringify({ type: 'ping' })), 10_000);
// Serveur : si aucun ping pendant 30 s → marquer offline

SymptômeMieux
WebSocket pour des notifications uniformesSSE — moins coûteux, gestion native
1 WS par sous-feature (chat, présence, notif)1 seul WS multiplexé avec types de messages
Pas de heartbeatConnexion morte non détectée → présence faussée
Sticky sessions oubliéesLB round-robin casse les WS au reload
Pas de reconnect côté clientUX cassée à la moindre coupure
Stocker les messages dans le WS uniquementPersiste en DB sinon perte au redémarrage
CRDT pour des entités relationnelles complexesCRDT bril sur du texte / structures arborescentes ; pour de la donnée transactionnelle, Postgres + WebSocket sont mieux

Tu construis un dashboard live avec ~50 widgets qui se mettent à jour automatiquement (lecture seule). Choix idéal en 2026 ?
Tu codes un éditeur collaboratif type Notion. Deux utilisateurs offline éditent puis se reconnectent. Quelle approche ?
Ton app a 200k utilisateurs concurrents en pic, équipe de 3 backend. Tu hésites entre WebSocket maison et Pusher/Ably. Quel critère décide ?
Tu déploies une app WebSocket sur 3 nodes derrière un load balancer round-robin. Les utilisateurs voient leurs messages en double, ou pas du tout. Pourquoi ?


Suite : 16.3 — 3D, jeu, créatif — Canvas, WebGL, Three.js, WebGPU.