Aller au contenu

HTML over WebSockets : des SPAs temps réel sans JavaScript

·

html-over-websockets-spas-temps-reel-sans-javascript-featured

Le web est bizarre. On a passé quinze ans à construire des SPAs de plus en plus ambitieuses côté client : des bundles JavaScript qui appellent une API JSON, désérialisent, re-rendent, gèrent un état local, synchronisent avec le serveur. Et maintenant, une partie non négligeable de la communauté dit : « Et si on envoyait juste du HTML ? »

C’est la prémisse de l’approche « HTML over WebSockets ». Au lieu de laisser le client construire l’interface à partir de données JSON, c’est le serveur qui envoie directement des fragments HTML à injecter dans le DOM. Le JavaScript côté client se réduit à « reçois ce bout de HTML, colle-le là ». Pas de désérialisation, pas de composant qui recalcule son rendu, pas de store à synchroniser.

Ça paraît presque naïf — et c’est exactement pour ça que ça mérite qu’on s’y attarde sérieusement. L’article sur Hacker News qui a déclenché cette vague de débats (194 points, 136 commentaires) a mis le doigt sur quelque chose que beaucoup de devs ressentaient sans l’avoir formalisé : la complexité front-end a peut-être dépassé les besoins réels de la majorité des projets web.

TL;DR — HTML over WebSockets, c’est envoyer des fragments HTML depuis le serveur via une connexion persistante, au lieu de JSON + rendu côté client. Les frameworks comme Phoenix LiveView, Hotwire/Turbo Streams ou htmx popularisent cette approche. Résultat : des interfaces temps réel avec très peu de JavaScript — mais avec de vraies contraintes d’infrastructure à anticiper.

Le principe : le serveur reprend les rênes du rendu

Dans une SPA classique, le serveur répond en JSON. Le client reçoit {"user": {"name": "Alice", "score": 42}}, et c’est React, Vue ou Svelte qui décide comment l’afficher. Ce découplage est puissant — il permet des clients mobiles, des micro-frontends, des architectures sophistiquées. Mais il a un coût réel : tu maintiens un état applicatif côté client, tu gères les erreurs de synchronisation, et tu livres des centaines de kilobytes de JS à chaque visiteur avant qu’il voie quoi que ce soit.

HTML over WebSockets inverse le flux. Le serveur maintient l’état. Quand quelque chose change — un utilisateur envoie un message, un stock se met à jour, une alerte se déclenche — le serveur envoie directement le fragment HTML correspondant : <li id="msg-42">Alice : Salut</li>. Le client reçoit ça et l’insère dans le DOM à l’endroit prévu. La connexion WebSocket reste ouverte en permanence, ce qui permet des mises à jour vraiment temps réel, sans polling ni long-polling.

Ce n’est pas une idée nouvelle — les applications Smalltalk des années 80 fonctionnaient déjà sur ce principe de serveur qui pilote l’affichage. Ce qui est nouveau, c’est qu’on a enfin des outils matures et performants pour faire ça à l’échelle du web moderne, avec du diff intelligent et des connexions qui tiennent des centaines de milliers d’utilisateurs simultanés.

Les frameworks qui incarnent cette approche

Phoenix LiveView (Elixir) est probablement le précurseur le plus abouti. Il ne renvoie pas le HTML complet à chaque update — il calcule un diff entre l’état précédent et le nouvel état, et n’envoie que les parties qui ont changé, sous une forme binaire compressée. Le résultat : des mises à jour ultra-légères, même sur des interfaces complexes. Les benchmarks documentés parlent de 200 000 connexions simultanées sur un serveur bien dimensionné. C’est Erlang/OTP sous le capot, et ça se voit.

Hotwire / Turbo Streams (l’écosystème Rails, adopté aussi par Laravel) prend une approche différente. Les Turbo Streams définissent des actions explicites — append, replace, remove, prepend — appliquées sur des éléments ciblés par leur ID HTML. C’est moins magique que LiveView mais très pragmatique pour des projets Rails existants. Tu ajoutes du temps réel à une app monolithique sans la réécrire.

htmx avec son extension WebSocket est l’option la plus agnostique côté stack. Tu ajoutes un attribut hx-ext="ws" et ws-connect="/ws" sur un élément, et htmx gère la connexion et les swaps DOM. Tu peux l’intégrer avec Django, FastAPI, Express, Laravel — peu importe le back-end. C’est la solution la plus légère à adopter progressivement sur un projet existant.

Un exemple concret pour voir de quoi on parle

Prenons un dashboard de stats temps réel. Avec une SPA classique : un endpoint REST, un polling ou un WebSocket qui renvoie du JSON, et un composant Vue/React qui reçoit les données et re-rend l’affichage. Côté client, tu gères l’état, les erreurs réseau, le chargement initial, les transitions.

Avec HTML over WebSockets, côté serveur (pseudo-code Django Channels + htmx) :

DÉCOUVREZ NOS RÉALISATIONS

Capture d'écran du site Boucherie Foulon réalisé par Westcode

Boucherie Foulon

Site vitrine WordPress pour un artisan boucher a Vaiges (53). Photographie, mise en valeur des produits et communication web.

Voir le projet →
# Côté serveur (Django Channels)
async def send_stats_update(consumer, new_stats):
    html = render_to_string("widgets/stats.html", {"stats": new_stats})
    await consumer.send(text_data=html)

# Le template stats.html
# 
# {{ stats.visitors }} visiteurs actifs #

Et côté HTML client :

C’est tout. Quand les stats changent, le serveur re-rend le template Django avec les nouvelles données et envoie le HTML résultant via WebSocket. L’attribut hx-swap-oob permet de cibler plusieurs zones de la page en une seule transmission. Zéro logique JS côté client à écrire.

L’avantage réel de cette approche n’est pas d’écrire « moins de JavaScript » — c’est de n’avoir qu’un seul état à maintenir. La source de vérité reste sur le serveur, et les bugs de désynchronisation (le client qui croit que le stock est à 3 alors que le serveur dit 0) disparaissent structurellement.

Les vrais avantages, pas les arguments marketing

On présente souvent ça comme « moins de JavaScript ». C’est vrai, mais c’est le bénéfice secondaire. Le gain principal, c’est la simplification radicale de la gestion d’état. Plus de Redux, plus de Pinia, plus de synchronisation state client / réponse serveur. L’état vit sur le serveur, le client affiche ce qu’on lui dit d’afficher. Sur des applications où la logique métier est complexe, c’est une économie de complexité architecturale considérable.

Deuxième avantage concret : la DX pour les équipes back-end. Un dev PHP, Python ou Ruby peut construire une interface dynamique temps réel sans écrire une ligne de TypeScript. Quand tu développes une application métier sur mesure pour une PME — outil de suivi de production, tableau de bord logistique, interface de gestion RH — tu n’as plus besoin d’une expertise front-end dédiée pour chaque feature interactive. Un développeur full-stack maîtrisant son framework back peut aller au bout.

Le SEO aussi s’en sort mieux par défaut. Puisque le rendu HTML vient du serveur, les crawlers voient un contenu réel dès le premier byte — pas une balise <div id="app"></div> vide qui attend que 300 KB de JS se chargent. Pour des projets e-commerce ou des sites à fort enjeu de référencement, c’est un argument qui compte.

L’avantage décisif de HTML over WebSockets, ce n’est pas d’écrire moins de JavaScript — c’est de ne jamais avoir à synchroniser deux états qui divergent silencieusement.

Les limites réelles, sans les minimiser

Premièrement, la latence perçue. Chaque interaction passe par le réseau : clic → WebSocket → serveur → traitement → HTML → retour → swap DOM. Sur une fibre en France, c’est imperceptible. Sur une connexion mobile dégradée ou depuis l’autre bout du monde, ça se sent. Les SPAs avec état local restent supérieures pour les interactions ultralocales — drag & drop, formulaires à validation instantanée, animations complexes. Phoenix LiveView propose des « optimistic updates » pour compenser, mais c’est de la complexité ajoutée qui grignote le bénéfice initial.

Deuxièmement, le scaling horizontal devient non trivial. Si chaque utilisateur maintient une connexion WebSocket persistante avec un serveur qui garde son état en mémoire, tu ne peux pas balancer un load balancer naïf devant dix instances sans casser les sessions. Il faut soit du sticky sessions, soit un backend de state partagé (Redis, ETS pour Erlang). C’est gérable, mais ça demande de l’architecture et un hébergement bien dimensionné dès le départ.

Troisièmement, certaines interactions riches restent difficiles : éditeurs collaboratifs type Notion, jeux temps réel, manipulations canvas, interfaces offline-capable. Pour ces cas, JavaScript côté client reste irremplaçable. HTML over WebSockets ne prétend pas remplacer React pour construire un Figma — c’est un outil différent, pas un outil universel.

WESTCODE BY LEB

Studio web en Mayenne — On crée des outils numériques qui transforment

⚠️ Chaque connexion WebSocket ouverte consomme des ressources serveur (mémoire, descripteurs de fichiers). Sur du grand public avec 10 000 utilisateurs simultanés, ça change radicalement l’équation d’infrastructure par rapport à un backend REST stateless. Pense à dimensionner — ou à utiliser un service dédié pour les WebSockets (Pusher, Ably, etc.).

Quand l’adopter — et quand s’en passer

Ce pattern brille particulièrement sur les tableaux de bord internes, les outils métier, les notifications temps réel, les fils de commentaires live, ou les interfaces de monitoring. Des cas où le temps réel est nécessaire mais où la complexité front n’est pas justifiée. Si tu construis un outil de reporting qui se rafraîchit en continu, ou une interface de gestion de tickets avec updates live, Phoenix LiveView ou htmx + WebSockets va te faire gagner des semaines de développement par rapport à une SPA React complète.

En revanche, si tu construis une application mobile-first avec des animations sophistiquées, un éditeur collaboratif, une expérience offline-capable ou un produit dont l’UX est le cœur de la valeur — reste sur React, Vue ou Svelte avec une API bien conçue. HTML over WebSockets est un outil dans ta boîte à outils, pas un dogme à appliquer partout.

La bonne question à se poser avant de choisir : « Est-ce que mes interactions ont besoin d’un état complexe côté client, ou est-ce que je veux simplement afficher ce que le serveur sait ? » Si c’est la deuxième option dans 80% des cas, tu peux très probablement simplifier drastiquement ta stack — et te concentrer sur la logique métier plutôt que sur la plomberie front.


Mon avis tranché : on a sur-indexé sur les SPAs pendant une décennie entière. L’engouement autour de React a fait croire que toute interface interactive avait besoin d’un bundle JS côté client, d’un état applicatif front, et d’une équipe dédiée pour l’entretenir. HTML over WebSockets, c’est le retour de balancier sain — pas un recul technologique, mais la reconnaissance que le rendu serveur n’est pas une technique de pauvre.

Les équipes qui utilisent Phoenix LiveView depuis quelques années rapportent des économies de 40 à 60% de temps de développement sur des features temps réel. Ce ne sont pas des chiffres marketing, ce sont des retours de terrain de développeurs qui ont comparé les deux approches sur des projets concrets. Et quand on ajoute la simplicité de maintenance à long terme — un seul langage, un seul état, une seule couche à debugger — l’argument devient difficile à ignorer.

Mon pari : dans cinq ans, la majorité des dashboards, back-offices et outils internes seront construits sur ce pattern ou un équivalent proche. Pas parce que c’est la nouvelle mode de HN, mais parce que ça marche, ça se maintient, et ça coûte objectivement moins cher à construire et à faire évoluer. La vraie question, c’est si la communauté JavaScript l’acceptera honnêtement — ou si elle inventera une nouvelle couche d’abstraction pour éviter de l’admettre.