JavaScript Server-Sent Events (EventSource)
Apprenez les Server-Sent Events JavaScript avec l'API EventSource — recevez un flux unidirectionnel de mises à jour du serveur via HTTP, avec reconnexion automatique et événements nommés.
Les Server-Sent Events (SSE) sont un moyen standard pour un serveur d'envoyer un flux continu et unidirectionnel de mises à jour textuelles au navigateur via une connexion HTTP unique et persistante. Au lieu que le client interroge répétitivement un point de terminaison avec fetch en demandant « y a-t-il quelque chose de nouveau ? », la connexion reste ouverte et le serveur écrit des messages chaque fois qu'il a quelque chose à envoyer. Le navigateur expose cela via l'interface intégrée EventSource, donc vous n'avez besoin d'aucune bibliothèque externe pour consommer un flux.
Les SSE brillent lorsque les mises à jour circulent dans un seul sens — du serveur vers le client. Les flux d'actualités en direct, les notifications, les barres de progression de build ou d'upload, les tableaux de bord de surveillance et les cotations boursières sont tous des cas d'usage naturels.
Recevoir un flux avec EventSource
Ouvrir un flux ne prend qu'une seule ligne. Vous créez un EventSource pointant vers un point de terminaison serveur, puis vous attachez des gestionnaires pour les événements qu'il déclenche.
const es = new EventSource('/events');
es.onmessage = (event) => {
console.log('New message:', event.data);
};
es.onopen = () => {
console.log('Connection opened');
};
es.onerror = (error) => {
console.log('Connection error:', error);
};Quelques points méritent d'être notés :
- La connexion s'ouvre dès que vous construisez l'
EventSource. onmessagese déclenche pour chaque message par défaut (sans nom) que le serveur envoie. La charge utile est toujours une string, disponible surevent.data.onopense déclenche une fois que la connexion est établie (et à nouveau après une reconnexion).onerrorse déclenche lorsque la connexion est interrompue ou échoue. Contrairement à unfetchqui échoue, cela ne signifie pas nécessairement que vous devez abandonner — le navigateur essaiera généralement de se reconnecter automatiquement (plus de détails ci-dessous).
Voici un exemple légèrement plus complet qui analyse les charges utiles JSON et met à jour la page :
const es = new EventSource('/notifications');
es.onmessage = (event) => {
const data = JSON.parse(event.data);
const li = document.createElement('li');
li.textContent = data.message;
document.querySelector('#feed').append(li);
};Le format de transmission
Le serveur répond avec le type de contenu text/event-stream et écrit du texte UTF-8 brut dans un format simple basé sur des lignes. Chaque message est un bloc de lignes champ: valeur, et une ligne vide marque la fin d'un message.
data: Hello there
data: First line
data: Second line
event: priceUpdate
data: {"symbol":"ACME","price":42.10}
id: 42
data: This message has an id
retry: 10000
data: Reconnect after 10 seconds next timeLes champs reconnus sont :
data:— la charge utile du message. Si un message comporte plusieurs lignesdata:, elles sont jointes avec un saut de ligne (\n) avant d'être transmises en tant queevent.data.event:— un nom d'événement optionnel. Sans lui, le message est transmis àonmessage.id:— un identifiant optionnel que le navigateur mémorise pour la reconnexion.retry:— le délai de reconnexion en millisecondes.
Les lignes commençant par deux-points (:) sont des commentaires et sont ignorées — les serveurs les envoient souvent comme pings de maintien de connexion.
Événements nommés
Par défaut, chaque message est envoyé à onmessage. Un serveur peut à la place étiqueter un message avec un champ event:, et le client écoute ce nom spécifique avec addEventListener. Cela vous permet d'acheminer différents types de mises à jour vers différents gestionnaires sur la même connexion.
Étant donné un flux comme celui-ci :
event: priceUpdate
data: {"symbol":"ACME","price":42.10}
event: newsAlert
data: Markets closed early today
data: a plain default messageVous câbleriez le client de cette façon :
const es = new EventSource('/market');
es.addEventListener('priceUpdate', (event) => {
const { symbol, price } = JSON.parse(event.data);
console.log(`${symbol} is now ${price}`);
});
es.addEventListener('newsAlert', (event) => {
console.log('News:', event.data);
});
// Messages with no `event:` field still land here.
es.onmessage = (event) => {
console.log('Default message:', event.data);
};Reconnexion automatique
C'est la fonctionnalité qui distingue les SSE du streaming manuel avec fetch. Si la connexion est interrompue — un réseau instable, un redémarrage du serveur, un délai d'expiration de proxy — le navigateur se reconnecte automatiquement après un court délai. Vous n'avez pas à écrire de boucle de réessai.
Pour rendre la reprise fiable, le navigateur garde en mémoire le dernier id: reçu. À la reconnexion, il renvoie cette valeur dans un en-tête de requête Last-Event-ID, afin que le serveur puisse reprendre exactement là où le flux s'était arrêté plutôt que de tout rejouer.
GET /events HTTP/1.1
Last-Event-ID: 42
Accept: text/event-streamLe serveur peut également régler le délai avant la prochaine reconnexion en envoyant un champ retry: (en millisecondes) :
retry: 5000
data: The browser will wait 5 seconds before reconnecting if droppedLa reconnexion automatique ne se produit que lorsque la connexion est considérée comme récupérable. Si le serveur répond à une reconnexion avec une erreur HTTP telle que 204 No Content ou un statut 4xx, le navigateur considère le flux comme terminé et arrête d'essayer.
État de la connexion et fermeture
Un EventSource expose son état actuel via la propriété readyState, qui correspond à trois constantes :
EventSource.CONNECTING(0) — connexion en cours, ou en attente de reconnexion.EventSource.OPEN(1) — connecté et en cours de réception.EventSource.CLOSED(2) — fermé et ne se reconnectera pas.
Lorsque vous n'avez plus besoin du flux, appelez es.close(). Cela ferme immédiatement la connexion, et — point important — le navigateur ne se reconnectera pas ensuite.
const es = new EventSource('/events');
// Stop listening after 30 seconds.
setTimeout(() => {
es.close();
console.log('readyState is now', es.readyState); // 2 (CLOSED)
}, 30000);Requêtes cross-origin et identifiants
Par défaut, un EventSource suit la politique de même origine. Pour diffuser depuis une origine différente, le serveur doit envoyer les en-têtes CORS appropriés (tels que Access-Control-Allow-Origin).
Les cookies ne sont pas envoyés lors de requêtes cross-origin, sauf si vous l'activez explicitement. Passez l'option withCredentials, et assurez-vous que le serveur autorise les identifiants dans sa configuration CORS :
const es = new EventSource('https://api.example.com/events', {
withCredentials: true,
});Une note côté serveur
Les SSE sont principalement une fonctionnalité du navigateur, mais le serveur doit coopérer. Quel que soit le langage utilisé, la recette est la même : répondre avec Content-Type: text/event-stream, maintenir la connexion ouverte au lieu de terminer la réponse, et écrire des blocs au format événement au fur et à mesure que les données deviennent disponibles.
Voici un exemple minimal avec Node.js pour illustrer la structure :
import http from 'node:http';
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
let id = 0;
const timer = setInterval(() => {
id += 1;
res.write(`id: ${id}\n`);
res.write(`data: The time is ${new Date().toISOString()}\n\n`);
}, 1000);
// Stop the timer when the client disconnects.
req.on('close', () => clearInterval(timer));
}).listen(3000);Chaque bloc se termine par une ligne vide (\n\n) pour compléter le message. Le client connecté avec new EventSource('http://localhost:3000') recevrait une mise à jour par seconde.
Limitations
Les SSE transmettent du texte uniquement (UTF-8) — il n'y a pas de type de trame binaire, donc vous ne pouvez pas diffuser des octets bruts comme des images ou de l'audio sans les encoder d'abord. C'est également strictement unidirectionnel : pour envoyer des données au serveur, vous avez toujours besoin d'une requête normale via fetch ou XMLHttpRequest, ou d'une connexion WebSocket en full duplex.
Une autre limite pratique : sur HTTP/1.1, les navigateurs plafonnent le nombre de connexions simultanées à une même origine à environ six. Puisque chaque EventSource maintient une connexion ouverte, ouvrir de nombreux onglets vers le même site peut épuiser ce budget. Cela est bien moins problématique sur HTTP/2, où de nombreux flux sont multiplexés sur une seule connexion.
SSE vs. WebSockets
Les SSE et les WebSockets transmettent tous deux des données en temps réel, mais ils résolvent des problèmes différents.
| Server-Sent Events | WebSockets | |
|---|---|---|
| Direction | Unidirectionnel (serveur → client) | Bidirectionnel (full duplex) |
| Protocole | HTTP simple | Mise à niveau ws:// / wss:// |
| Données | Texte (UTF-8) uniquement | Texte et binaire |
| Reconnexion | Automatique, intégrée | À implémenter soi-même |
| Idéal pour | Flux, notifications, progression | Chat, jeux, édition collaborative |
Choisissez les SSE lorsque le navigateur a seulement besoin d'écouter — notifications, flux en direct, tableaux de bord, mises à jour de progression. Sa reconnexion automatique, son transport HTTP simple et sa petite API client en font l'outil le plus simple pour le travail. Choisissez les WebSockets lorsque le client doit aussi envoyer fréquemment, lorsque vous avez besoin de données binaires, ou lorsque la latence par message est importante, comme dans les applications de chat et les jeux multijoueurs.
Si vos données se trouvent déjà derrière un flux asynchrone basé sur des promesses, associer les gestionnaires SSE à async/await pour toute requête de suivi (par exemple, récupérer les détails complets lorsqu'une notification légère arrive) maintient votre code propre et lisible.