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é
Le paysage des protocoles
Section intitulée « Le paysage des protocoles »HTTP polling → demande périodique simple (gaspille)HTTP long-polling → connexion ouverte jusqu'à eventSSE (Server-Sent) → flux unidirectionnel server → clientWebSocket → bidirectionnel, full-duplexWebRTC → P2P, vidéo / audio / data, NAT traversalWebTransport (HTTP/3) → bidirectionnel, multi-stream, encore jeune en 2026| Protocole | Direction | Quand l’utiliser |
|---|---|---|
| HTTP polling | request/response | Très peu d’updates (1×/min) |
| Long-polling | client → server tient la connexion | Fallback si WS bloqué |
| SSE | server → client (uni) | Notifications, dashboards lecture seule, IA streaming |
| WebSocket | bidirectionnel | Chat, présence, collab |
| WebRTC | P2P bidirectionnel | Vidéo / audio / data peer-to-peer |
| WebTransport | multi-stream HTTP/3 | Cas 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).
Server-Sent Events — sous-coté
Section intitulée « Server-Sent Events — sous-coté »En 5 lignes
Section intitulée « En 5 lignes »- 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.
Côté serveur (Hono)
Section intitulée « Côté serveur (Hono) »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) }); } }));Côté client
Section intitulée « Côté client »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');Quand SSE ?
Section intitulée « Quand SSE ? »- Notifications, dashboards live (read-only).
- LLM streaming (ChatGPT, Claude API renvoient en SSE).
- Logs en direct.
- Position d’une commande / livraison.
Quand pas SSE ?
Section intitulée « Quand pas SSE ? »- Bidirectionnel (chat, collab) → WebSocket.
- Binaire pur (jeu, vidéo) → WebSocket binaire ou WebRTC.
WebSocket — le standard temps réel
Section intitulée « WebSocket — le standard temps réel »En 5 lignes
Section intitulée « En 5 lignes »- 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).
Côté serveur (Hono + ws)
Section intitulée « Côté serveur (Hono + ws) »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));});Côté client
Section intitulée « Côté client »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());Authentification
Section intitulée « Authentification »WebSockets n’ont pas de header Authorization (sauf hack en sub-protocol). Patterns 2026 :
| Pattern | Explication |
|---|---|
| Cookie session | Si même origine — WS hérite des cookies |
| Token dans l’URL | wss://api/ws?token=xxx — attention aux logs |
| Sec-WebSocket-Protocol | new WebSocket(url, ['Bearer.eyJxxx']) — astuce courante |
| Auth après handshake | Premier message = auth, kick si raté en 5s |
Coûts cachés
Section intitulée « Coûts cachés »- Mémoire : ~10-50 KB / connexion. 100k WS = 5 GB RAM minimum.
- Sticky sessions : load-balancer doit router vers le même node —
Cookieou 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.
Realtime managés
Section intitulée « Realtime managés »| Service | Force | Modèle |
|---|---|---|
| Pusher Channels | Mature, simple | $/messages |
| Ably | Très solide, multi-région | $/messages, generous free |
| Supabase Realtime | Postgres CDC + presence | Inclus avec Supabase |
| PartyKit | Cloudflare Durable Objects | Edge global, par room |
| Liveblocks | Specialised collab + presence | $/MAU |
| Pusher Beams | Push mobile cross-platform | $ |
| Centrifugo | OSS self-host | gratuit |
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 — P2P pour la vidéo / audio
Section intitulée « WebRTC — P2P pour la vidéo / audio »WebRTC est complexe. À ne mettre en œuvre qu’en sachant ce qu’on fait ou via une lib qui abstrait.
Le triangle
Section intitulée « Le triangle »┌────────┐ ┌────────┐│ Peer A │ ── DataChan ── │ Peer B │└────┬───┘ └───┬────┘ │ │ └────── STUN / TURN ─────┘ │ ┌────────┐ │ Signal │ (WebSocket, your server) └────────┘| Brique | Rôle |
|---|---|
| STUN | Découvre l’IP publique derrière NAT |
| TURN | Relai si NAT traversal échoue (~20 % des cas en pratique) |
| Signaling | Échange initial (SDP, ICE) — pas standardisé, ton choix |
| DataChannel | Tunnel binaire / texte P2P |
| MediaStream | Audio / vidéo capturé via getUserMedia |
Quand le faire seul
Section intitulée « Quand le faire seul »Très rarement. Sauf :
- Tu construis un produit niche (jeu temps réel P2P).
- Tu veux contrôler à 100 %.
Quand utiliser une SFU / lib
Section intitulée « Quand utiliser une SFU / lib »| Outil | Quoi |
|---|---|
| LiveKit | OSS self-host ou cloud, SFU mûre |
| mediasoup | OSS Node-friendly, plus bas niveau |
| Janus | OSS C, pour les fous |
| Agora.io / Daily.co / Twilio Video | SaaS 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 »Le problème
Section intitulée « Le problème »Deux utilisateurs éditent la même phrase hors-ligne, puis se reconnectent. Comment merger ?
| Approche | Souci |
|---|---|
| Last-write-wins | On perd les éditions de l’un |
| Lock | Pas de collab vraie |
| OT (Operational Transform) | Compliqué (Google Docs au début) |
| CRDT | Mathématiquement convergent — pas de central authority |
CRDT en 5 lignes
Section intitulée « CRDT en 5 lignes »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 — le standard 2026
Section intitulée « Yjs — le standard 2026 »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ésytext.insert(0, 'Hello ');Types CRDT supportés par Yjs :
| Type Yjs | Équivalent JS |
|---|---|
Y.Text | string |
Y.Array | Array |
Y.Map | Map |
Y.XmlFragment | DOM XML |
Bindings frameworks
Section intitulée « Bindings frameworks »y-prosemirror/y-tiptap— éditeurs riches collaboratifs (style Notion).y-codemirror/y-monaco— éditeurs de code collab (VSCode-like).yjs-tldraw— canvas collaboratif.
Awareness (présence)
Section intitulée « Awareness (présence) »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); }});Backend Yjs
Section intitulée « Backend Yjs »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.
Persistance
Section intitulée « Persistance »CRDT en mémoire ne survit pas au redémarrage. Stratégies :
y-leveldbouy-mongodbcôté serveur.- Snapshot régulier (toutes les N modifs) en DB classique.
- Liveblocks / Hocuspocus persistent automatiquement.
Automerge — l’alternative
Section intitulée « Automerge — l’alternative »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.
Patterns d’architecture realtime
Section intitulée « Patterns d’architecture realtime »Pattern 1 — Pub/Sub centralisé
Section intitulée « Pattern 1 — Pub/Sub centralisé »Client A ──┐Client B ──┼── WebSocket ── App ──┐Client C ──┘ │ ▼ Redis Pub/Sub ◄── Worker batchSimple, scalable jusqu’à ~50k clients par node.
Pattern 2 — Sticky session + Redis fan-out
Section intitulée « Pattern 2 — Sticky session + Redis fan-out »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.
Pattern 4 — P2P pur (WebRTC DataChannel)
Section intitulée « Pattern 4 — P2P pur (WebRTC DataChannel) »Pas de serveur applicatif. Idéal pour jeux, doc local. Mais STUN/TURN restent indispensables.
Présence (online / typing / cursor)
Section intitulée « Présence (online / typing / cursor) »Au-delà des messages, tu veux savoir qui est là, qui tape, où sont les curseurs.
| Solution | Quand |
|---|---|
| Y.awareness | Si tu utilises Yjs (gratuit) |
| Liveblocks Presence | Si tu utilises Liveblocks |
| Custom heartbeat WS | DIY simple |
| Phoenix Channels | Si stack Elixir |
Heartbeat custom :
// Client : ping toutes les 10 ssetInterval(() => ws.send(JSON.stringify({ type: 'ping' })), 10_000);
// Serveur : si aucun ping pendant 30 s → marquer offlineAnti-patterns
Section intitulée « Anti-patterns »| Symptôme | Mieux |
|---|---|
| WebSocket pour des notifications uniformes | SSE — moins coûteux, gestion native |
| 1 WS par sous-feature (chat, présence, notif) | 1 seul WS multiplexé avec types de messages |
| Pas de heartbeat | Connexion morte non détectée → présence faussée |
| Sticky sessions oubliées | LB round-robin casse les WS au reload |
| Pas de reconnect côté client | UX cassée à la moindre coupure |
| Stocker les messages dans le WS uniquement | Persiste en DB sinon perte au redémarrage |
| CRDT pour des entités relationnelles complexes | CRDT bril sur du texte / structures arborescentes ; pour de la donnée transactionnelle, Postgres + WebSocket sont mieux |
Auto-évaluation
Section intitulée « Auto-évaluation »Pour aller plus loin
Section intitulée « Pour aller plus loin »- Yjs docs — docs.yjs.dev
- Automerge — automerge.org
- Liveblocks docs — liveblocks.io/docs
- PartyKit — partykit.io
- LiveKit — livekit.io
- MDN — Server-Sent Events — developer.mozilla.org/…/Server-sent_events
- Designing Data-Intensive Applications — Martin Kleppmann (chapitres replication / consensus)
Suite : 16.3 — 3D, jeu, créatif — Canvas, WebGL, Three.js, WebGPU.