Progresser vers des interfaces réellement réactives ne suppose pas forcément de basculer en SPA. Entre Server-Sent Events (SSE), WebSockets et Long Polling, le paysage du “temps réel” peut sembler flou. Bonne nouvelle : l’écosystème Symfony propose Mercure, un hub SSE pensé pour la simplicité, la sécurité et la scalabilité. Voici comment choisir la bonne approche selon vos contraintes de latence, de trafic, de compatibilité navigateur et de coûts, et quand adopter Mercure dans votre projet.
Les trois paradigmes de base
Long Polling
- Principe : le client fait une requête HTTP qui reste ouverte jusqu’à ce qu’un événement survienne (ou un timeout), puis relance immédiatement.
- Avantages : compatible partout, simple à mettre en place.
- Limites : gaspille des connexions, augmente la charge serveur et la latence perçue, peu efficace à grande échelle.
Server-Sent Events (SSE)
- Principe : une connexion HTTP unidirectionnelle du serveur vers le client, qui “pousse” des événements textuels.
- Avantages : très simple côté client (EventSource), traverse bien les proxys/HTTP/2, auto-reconnexion native, idéal pour notifications, mises à jour d’état, dashboards.
- Limites : sens unique (client → serveur via POST/REST classique), pas de binaire, flux par onglet.
WebSockets
- Principe : connexion full-duplex persistante (client ↔ serveur).
- Avantages : latence très basse, échanges fréquents bidirectionnels, adapté aux chats très interactifs, jeux, co-édition temps réel intense.
- Limites : complexité infra (gestion de millions de connexions, sticky sessions parfois nécessaires), proxies/équilibreurs à configurer, coûts mémoire/CPU par connexion.
Critères pratiques pour trancher
-
Latence et type d’interaction
- SSE : excellent pour push serveur → client en quasi temps réel (notifications, progression, données métiers).
- WebSockets : requises pour interactions bidirectionnelles fréquentes ou synchronisation très fine.
- Long Polling : solution de secours ou pour de très faibles volumes.
-
Scalabilité et charge
- SSE : facile à mettre derrière un reverse proxy, good citizen HTTP/2; le fan-out reste le point clé (nombre de clients par sujet).
- WebSockets : nécessite une architecture pub/sub robuste, une gestion de la présence et du fan-out, et une observabilité fine.
- Long Polling : coûteux en connexions et CPU, à éviter en charge soutenue.
-
Compatibilité navigateur et réseau
- SSE : supporté par tous les navigateurs modernes; IE ancien = polyfills/contournements. Fonctionne bien via proxies.
- WebSockets : large support, mais attention aux proxies d’entreprise et aux timeouts agressifs.
- Long Polling : universel mais peu efficace.
-
Sécurité et authentification
- SSE avec Mercure : gestion par JWT de l’abonnement aux “topics”, contrôle d’accès fin.
- WebSockets : gérer l’auth initiale et la réauth périodique, signature des messages, multi-tenancy.
- Long Polling : mêmes contraintes que HTTP traditionnel, mais multiplication de requêtes.
-
Coûts
- SSE (Mercure) : faible complexité de déploiement, resources stables, coûts raisonnables pour la majorité des cas B2B et SaaS.
- WebSockets : coûts d’infrastructure et d’expertise plus élevés, surtout à très grande échelle.
- Long Polling : coûts cachés (CPU, bande passante) qui montent vite.
Mercure dans l’écosystème Symfony : quand c’est le bon choix
Mercure est un hub SSE et un protocole conçu pour diffuser des événements en temps réel, avec une intégration première classe dans Symfony via MercureBundle et API Platform. Il permet de publier des mises à jour vers des “topics” sécurisés par JWT et gère la reconnexion (Last-Event-ID), le multiplexage de sujets et la diffusion efficace.
Choisissez Mercure si :
- Vous poussez des données serveur → client (notifications, états de jobs, KPIs en dashboard).
- Vous avez déjà une API (REST/JSON-LD, API Platform) et souhaitez diffuser les changements des ressources.
- Vos interactions client → serveur peuvent rester en HTTP classique (POST/PUT/PATCH).
- Vous voulez limiter la complexité infra tout en conservant une très bonne expérience “temps réel”.
Évitez Mercure/SSE seul si :
- Vous avez besoin d’échanges bidirectionnels fréquents à très faible latence (chat tap-tap, multi-joueurs, co-édition milliseconde).
- Vous devez échanger du binaire ou un très grand volume de messages éphémères dans les deux sens.
Astuce complémentaire à Symfony UX & Live Components : utilisez Live Components pour des interactions locales et formulaires réactifs, et Mercure pour le push serveur (synchronisation inter-onglets, notifications back-office, rafraîchissements automatiques de listes).
Scénarios concrets
1) Notifications back-office et barres de progression
- Besoin : afficher instantanément la fin d’un import ou d’un traitement Messenger.
- Solution : publier un événement Mercure quand le handler Messenger termine.
Exemple de configuration minimaliste:
## config/packages/mercure.yaml
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt: '%env(MERCURE_JWT)%'
Publication depuis un contrôleur/handler:
<?php
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
final class ImportCompletedNotifier
{
public function __construct(private HubInterface $hub) {}
public function __invoke(string $jobId, array $payload): void
{
$topic = sprintf('https://exemple.com/imports/%s', $jobId);
$update = new Update($topic, json_encode([
'status' => 'done',
'count' => $payload['count'] ?? 0,
]), true); // true => privé si vous utilisez des JWT d'abonnement
$this->hub->publish($update);
}
}
Côté client:
const topic = encodeURIComponent('https://exemple.com/imports/1234');
const url = `https://mercure.exemple.com/.well-known/mercure?topic=${topic}`;
const es = new EventSource(url);
es.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.status === 'done') {
// mettre à jour l'UI
}
};
2) Dashboard d’indicateurs en temps réel
- Besoin : actualiser des KPI toutes les secondes sans recharger la page.
- Solution : vos workers/cron publient vers Mercure; le front écoute un ou plusieurs topics. Combinez avec Messenger pour découpler calcul et push.
3) Chat collaboratif très interactif
- Besoin : messages, statut “en train d’écrire”, présence, réactions en dessous de 100 ms.
- Recommandation : optez pour WebSockets (ex. passerelle dédiée ou service managé), et utilisez Messenger/Kafka/Redis pour le fan-out. Mercure peut coexister pour des notifications moins critiques.
4) IoT et télémétrie
- Besoin : push serveur → client pour observabilité.
- SSE via Mercure est souvent suffisant si vous maîtrisez le fan-out. Pour des centaines de milliers de connexions, étudiez l’edge compute et/ou une infra WebSockets spécialisée.
Anti-patterns à éviter
- Polling agressif par défaut (toutes les 1–5 s) au lieu de SSE/WS : vous payez en latence et en coûts.
- Diffuser des payloads énormes à chaque événement : préférez des diffs, un résumé ou une invalidation côté client.
- Mal gérer la reprise: n’omettez pas d’assigner des id aux événements si vous avez besoin de Last-Event-ID.
- Sur-utiliser des topics par utilisateur sans stratégie de groupement: explosion de connexions/subscriptions.
- Laisser un reverse proxy bufferiser le flux SSE: désactivez le buffering pour le endpoint Mercure (ex. Nginx: proxy_buffering off).
- Mettre de la logique métier lourde dans le hub: publiez des événements simples; la logique reste dans vos services/handlers.
- Oublier l’auth: avec Mercure, sécurisez l’abonnement aux topics via JWT; ne mettez jamais des données sensibles en clair sur des topics publics.
Scalabilité et coûts : points d’attention
-
SSE/Mercure
- Facile à déployer derrière Nginx/Traefik. Vérifiez les timeouts de connexion keep-alive.
- Fan-out: préférez des topics transverses par fonctionnalité (ex. “/projects/{id}/*”) plutôt qu’un topic par champ.
- Production: surveillez l’utilisation CPU/mémoire du hub, la taille des files d’attente, et la latence de diffusion.
-
WebSockets
- Prévoyez un plan de scaling horizontal avec pub/sub (Redis/NATS/Kafka), la gestion de la présence, et une stratégie de backpressure.
- Chiffrez systématiquement (wss), et surveillez la consommation mémoire par connexion.
-
Coûts
- Egress réseau et nombre de connexions simultanées sont les principaux postes.
- Les services managés (Pusher, Ably, etc.) peuvent être rentables au début, puis coûteux à grande échelle.
Compatibilité navigateur et réseau
-
SSE
- Support moderne excellent (Chrome, Firefox, Safari, Edge). IE ancien: prévoyez un fallback (polyfill) si votre audience le requiert.
- Mobile: attention aux onglets/activités en arrière-plan qui suspendent les connexions.
-
WebSockets
- Très bien supportés; certains proxys d’entreprise nécessitent une configuration spécifique.
-
Conseils généraux
- Limitez le nombre de connexions par page (regroupez les topics).
- Préférez HTTP/2 ou HTTP/3 côté reverse proxy quand c’est possible.
Arbre de décision express
- Vos échanges sont principalement serveur → client, à fréquence raisonnable, et vous voulez aller vite en restant robuste → privilégiez SSE via Mercure.
- Vous avez des interactions bidirectionnelles intensives et une exigence de latence très basse → WebSockets.
- Vous devez gérer des navigateurs/contraintes réseau hors normes et peu d’utilisateurs → Long Polling peut suffire, mais documentez la dette technique.
- Vous utilisez API Platform et voulez diffuser des mises à jour de ressources sans effort → Mercure est le choix naturel.
Mise en œuvre rapide avec Symfony
- Installer le bundle:
composer require symfony/mercure-bundle
- Configurer vos variables d’environnement (.env):
MERCURE_URL=https://mercure.exemple.com/.well-known/mercure
MERCURE_PUBLIC_URL=https://mercure.exemple.com/.well-known/mercure
MERCURE_JWT=<votre_jwt_de_publication>
- Déclarer le hub (config/packages/mercure.yaml):
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt: '%env(MERCURE_JWT)%'
- Publier un événement depuis un service/contrôleur (voir plus haut) et écouter via EventSource côté front.
Pour aller plus loin, reliez vos handlers Messenger à Mercure pour pousser les événements de fin de traitement, et combinez avec des Live Components pour un rendu progressif côté serveur.
Conclusion
Le “temps réel” n’est pas une technologie, mais un ensemble de compromis entre latence, simplicité et coûts. Dans l’écosystème Symfony, Mercure répond à la majorité des besoins serveur → client avec un excellent ratio valeur/complexité. Réservez les WebSockets aux cas vraiment interactifs et évitez le Long Polling en production à grande échelle.
Besoin d’un audit de vos besoins temps réel, d’un POC Mercure ou d’une migration depuis du polling ? Contactez-nous pour un atelier de cadrage et repartez avec un plan d’implémentation concret.