La première fois qu’on a chargé la web app d’un thermostat connecté milieu de gamme sur un Moto G4 throttlé en 3G, le LCP a mis 9,2 secondes. Pour allumer un radiateur. Le bundle JavaScript dépassait les 4 Mo, principalement occupé par une bibliothèque de graphiques, un SDK analytics propriétaire et trois manières différentes d’écouter des événements WebSocket. Voilà où nous en sommes en 2026 : la maison intelligente s’est remplie de protocoles élégants et de passerelles locales, mais le front-end qu’on colle derrière est resté une usine à gaz parfaitement indifférente aux signaux de classement documentés par Google.
L’enjeu n’est plus simplement fonctionnel. Une interface domotique qui met plus de trois secondes à devenir interactive n’est pas seulement frustrante, elle pénalise la découvrabilité organique du service associé au moment où les constructeurs cherchent à attirer de nouveaux utilisateurs via leur portail web. Et pourtant, les équipes embarquées traitent encore le navigateur comme une télécommande graphique secondaire, sans avoir intégré les contraintes des Core Web Vitals. Ce qu’on voit dans la Search Console de plusieurs acteurs de la domotique le confirme : les pages de contrôle sont massivement classées « faibles » en INP, et leur TTFB dépasse régulièrement la barre des 800 ms.
Pourquoi un simple dashboard IoT pèse 4 Mo de JavaScript
Le problème ne vient pas du protocole qui relie la passerelle au radiateur. Il vient de la manière dont on traduit cet état dans le DOM. Un dashboard typique charge un framework complet (React, Angular ou Vue), embarque une librairie de gestion d’état, un client MQTT over WebSocket, un wrapper pour les API locales, souvent une surcouche de notifications push et un outil d’analyse. Le premier réflexe des équipes est d’agréger tout cela dans un seul bundle, parce que l’application est perçue comme « une seule expérience ». Résultat : le thread principal passe les premières secondes à parser et compiler du code dont 70 % ne serviront pas avant la dixième interaction, voire jamais.
Pour aggraver les choses, la logique métier est fréquemment dupliquée. Un capteur de température peut voir son état transiter par le store global, être recalculé par un reducer, puis décoré par un middleware qui vérifie si l’utilisateur a ouvert le panneau de détail, avant de finalement modifier un seul <span>. On se retrouve avec plusieurs centaines de millisecondes de JavaScript exécuté pour un changement de valeur d’un octet. Si vous avez déjà inspecté un trace de performance dans Chrome DevTools sur ce type d’application, vous avez vu ce genre de cascade de tâches longues qui ne devraient jamais exister.
LCP étranglé par les widgets temps réel
Un affichage de température, un flux vidéo de sonnette, un graphique de consommation électrique en direct. Chaque widget supplémentaire injecte un bout de DOM lourd, parfois via une iframe, parfois via un composant asynchrone qui arrive tard dans le cycle de rendu. Le navigateur, en découvrant ces éléments après le chargement initial, recalcule la mise en page et retarde le moment où il peut considérer le plus gros élément visible comme stable. Ce n’est pas rare de voir le LCP se stabiliser à 4 secondes sur une simple page d’accueil domotique, non pas parce que le serveur est lent, mais parce qu’un graphique de puissance en SVG vient se peindre 1 800 ms après le reste.
Pire, certains développeurs positionnent une <div> de connexion ou un indicateur de batterie en haut à gauche avec une police custom chargée via une fonte web. Ce petit bloc, s’il est le plus grand élément visible, devient le candidat LCP malgré son inutilité. Décaler le chargement des polices secondaires et retirer les blocs décoratifs de la hiérarchie visuelle immédiate fait souvent gagner plusieurs centaines de millisecondes sans rien sacrifier à l’expérience utilisateur. Ces ajustements sont précisément ceux qu’on recommande quand on applique un plan d’action issu d’une bonne stratégie d’optimisation Core Web Vitals.
L’INP explose quand chaque interrupteur déclenche un rerender global
C’est le piège le plus sévère. Vous appuyez sur un bouton d’éclairage dans l’interface. L’utilisateur s’attend à voir l’icône changer immédiatement. Dans une architecture naïve, ce clic traverse un handler qui modifie le store principal, lequel notifie l’ensemble des composants abonnés. Le graphique de consommation, le badge de la porte d’entrée, l’arborescence des pièces : tous recalculent leurs props et leurs éléments virtuels, même si seule une classe CSS a besoin de basculer sur une icône. Le navigateur se retrouve avec une file de tâches de rendu qui repousse le prochain affichage, et l’outil de mesure INP de la Search Console enregistre un paquet de clics au-dessus de 300 ms.
Des approches comme l’utilisation d’un sélecteur d’état fin ou l’adoption d’une bibliothèque de gestion d’état atomique changent radicalement la donne sur ce type d’interactions. On a vu des dashboards passer d’un INP de 420 ms à 160 ms simplement en migrant d’un store Redux unique et normalisé vers une solution à granularité fine, comme Zustand avec des slices isolés, un sujet qu’on a documenté dans notre retour d’expérience sur l’adoption de Zustand pour la gestion d’état React. Le principe n’est pas seulement une question de préférence : chaque composant ne se réabonne qu’au fragment atomique qui le concerne, et le rerender est circonscrit à l’icône qui vient d’être pressée.
Matter et Thread ne sauveront pas votre bundle
La promesse des standards Matter et Thread, c’est la fin des hubs propriétaires et des latences imprévisibles causées par des aller-retours cloud. Un contrôleur local récupère l’état d’une ampoule en quelques millisecondes via une passerelle de bordure. Mais cette latence réseau quasi nulle ne réduit en rien le temps d’exécution du JavaScript qui va transformer cet état en nœud DOM. Vous pouvez recevoir la réponse en 2 ms ; si derrière la mise à jour de l’interface déclenche une boucle de layout de 200 ms, l’utilisateur ne verra aucune différence.
C’est un point de confusion courant dans les équipes multidisciplinaires. Les ingénieurs embarqués voient leur indicateur ping à 5 ms et considèrent le problème réglé, tandis que le front-end continue à bloquer le thread principal. La performance perçue d’une application de maison intelligente dépend entièrement du pipeline qui va de l’arrivée du message à la prochaine frame peinte. Les protocoles locaux aident, parce qu’ils évitent des timeouts réseau, mais ils ne compenseront jamais un composant React qui recalcule un arbre entier à chaque tick de capteur.
Trois ajustements qui divisent par deux le TBT
Un dashboard domotique de taille moyenne génère couramment 600 ms de Total Blocking Time au chargement. Voici ce qui fait la différence.
Déplacer les traitements hors du thread principal. Les flux MQTT ou WebSocket peuvent être ingérés par un Web Worker, qui normalise l’état et le pousse au store via un buffer limité en fréquence. Le thread graphique ne reçoit plus qu’un snapshot toutes les 300 ms, ce qui suffit largement pour des jauges de température.
Isoler chaque widget dans un composant chargé paresseusement. Un graphique de consommation électrique qui n’apparaît qu’après un scroll peut utiliser un IntersectionObserver et un import dynamique. On élimine du LCP tout ce qui n’est pas dans la fenêtre d’affichage initiale dès le premier octet.
Privilégier les primitives CSS aux animations JavaScript. Une icône qui change de couleur suite à une commande peut utiliser une transition CSS de 150 ms déclenchée par une classe, plutôt qu’une boucle requestAnimationFrame. Moins de code exécuté, moins de long tasks.
Ces ajustements ne demandent pas de réécrire la couche de communication IoT, ils modifient seulement la couche de présentation. On les a appliqués sur notre propre portail de gestion pour un petit parc de capteurs, et le TBT est passé de 580 ms à 210 ms en une demi-journée. Sans toucher une seule ligne de WebSocket.
Ce que l’IA générative change dans l’écriture de ces interfaces
Quand on a commencé à refondre notre dashboard maison, on a confronté deux approches assistées : laisser un LLM nous suggérer des refactors composant par composant dans un IDE classique, ou dialoguer directement avec un agent intégré au terminal capable de lire le bundle analyzer et de proposer des splits automatiques. L’expérience a confirmé que le dialogue continu avec l’outil d’analyse (lighthouse, Webpack bundle analyzer, traces Chrome) produit des recommandations plus pertinentes que les snippets isolés. On détaille cette comparaison dans notre analyse Claude Code vs Cursor IDE, mais le constat immédiat pour la domotique est clair : confier à un agent le soin de déplacer trois imports statiques vers du lazy loading en une séquence de terminal, c’est le moyen le plus direct de réduire le TBT sans introduire de régression fonctionnelle.
L’IA ne remplace pas la compréhension des Core Web Vitals, mais elle accélère considérablement l’application des correctifs une fois le diagnostic posé. Ce changement permet aux équipes produit de considérer la performance comme une itération continue plutôt que comme un projet de refonte qui prend trois mois.
Questions fréquentes
Faut-il développer une PWA pour sa box domotique plutôt qu’un site responsive classique ? Une PWA apporte un gain perceptible sur le LCP des visites répétées grâce au cache du Service Worker et à la possibilité d’un shell applicatif préchargé. En revanche, elle ne dispense pas de traiter les problèmes de poids du bundle et d’INP : un mauvais état interne mettra la PWA à genoux aussi vite qu’un site classique.
Comment gérer les mises à jour en arrière-plan des états de capteurs sans dégrader l’INP ? La méthode la plus fiable consiste à accumuler les mutations dans un Web Worker et à ne les transmettre au thread principal qu’à une fréquence plafonnée (par exemple 3 Hz). Le store côté navigateur ne reçoit qu’un objet patch que le framework peut appliquer en une seule transaction, ce qui évite les salves de micro-tâches qui étranglent l’interactivité.
Le lazy loading des composants du dashboard nuit-il au référencement des pages de contrôle ?
Si la page affiche du contenu visible sans action de l’utilisateur (jauges principales, interrupteurs essentiels), le lazy loading des widgets sous la ligne de flottaison ne pose aucun problème d’indexation. Googlebot rend désormais suffisamment de JavaScript pour exécuter un IntersectionObserver, et le contenu critique livré statiquement reste crawlable. Tant que vous ne masquez pas le contenu principal derrière une action utilisateur, le référencement ne bouge pas.