Accueil / Articles

Comment l’ingénierie frontend a évolué, du simple stylisme à la gestion de systèmes à grande échelle

L’ingénierie frontend est passée de HTML et CSS aux composants React, au caching, aux outils de monorepo et à l’observabilité. Qu’a changé à chaque étape et pourquoi ?

Éditeur : REACTAPP.TOP10 min de lecture2240 mots

Sources: I Thought Frontend Was Colors and Fonts. Then I Shipped to a Million Users.

Les outils ont évolué, mais notre façon de penser a encore plus progressé.

Si vous demandez à quelqu’un ce que signifie le développement frontend, vous obtiendrez généralement la même réponse : HTML, CSS, JavaScript. Couleurs, polices, images, mise en page.

C’est un bon point de départ. Mais ce n’est pas là que s’arrête l’histoire.

Ce qui a commencé par la création de pages web s’est transformé en l’ingénierie de systèmes complets qui doivent rester rapides, fiables et traçables alors que des milliers de personnes interagissent avec eux en même temps.

Les outils ont évolué dans ce sens. Mais ce qui a changé encore plus, c’est le ensemble de questions que nous avons appris à nous poser.

Voici le parcours complet.

1. Tout a commencé avec HTML, CSS et JavaScript

Le web des débuts gardait les choses simples.

HTML s’occupait de la structure. CSS gérait la présentation. JavaScript ajoutait une touche d’interactivité.

La mission se résumait en une seule phrase :

Créer une page qui ait l’air professionnelle et fonctionne correctement.

La plupart des pages étaient statiques. Les serveurs fournissaient du HTML complet, les navigateurs le rendaient affichable, et le navigateur lui-même ne contenait presque aucune logique.

Au fil du temps, les pages ont pris en charge davantage de responsabilités. Les formulaires ont commencé à valider les données saisies par l’utilisateur en temps réel. Le contenu se mettait à jour sans avoir besoin de recharger toute la page.

JavaScript a continué à assumer de plus en plus de tâches, sans jamais en transférer aucune.

  1. Des pages aux applications

Les sites web sont devenus des applications. Cela a entraîné l’apparition d’interfaces dynamiques, de requêtes vers des API, de formulaires en plusieurs étapes, de flux d’authentification, ainsi que de données stockées dans le navigateur entre deux interactions.

Ce changement a mis en évidence un problème que les anciennes techniques n’étaient pas conçues pour résoudre.

Lorsque vous manipulez directement le DOM, l’écran visible devient votre seule source de vérité. Une même donnée peut exister dans une variable, à l’intérieur d’un nœud DOM, et dans une réponse provenant du serveur. Rien ne garantit que ces trois éléments restent en accord.

Si vous mettez à jour l’un d’eux sans tenir compte des autres, vous finirez par avoir un bug qui ne se manifeste qu’après une séquence précise de clics.

Il fallait donc un moyen de définir à quoi devrait ressembler l’interface et de confier la tâche de déterminer les opérations réelles sur le DOM à un autre élément.

3. React : penser en composants

La véritable avancée de React n’a pas été sa syntaxe, mais le modèle mental qui la sous-tend.

UI = f(state)

Au lieu d’écrire element.appendChild(...), on écrit ce qui doit être vrai.

L’unité de travail de base a quitté la page complète pour se concentrer sur les composants. Pensez à un simple bouton, une carte affichant un produit, une boîte de dialogue ou une étape dans le processus de paiement. Chacun d’eux possède son propre marquage, son propre style et son propre comportement.

Voici ce que cette approche a permis d’obtenir :

Réutilisabilité. Construisez un composant une fois, puis utilisez-le en de nombreux endroits.

Raisonnement localisé. Vous pouvez comprendre un composant sans avoir à lire toute l’application.

Testabilité. Fournissez-lui des propriétés, vérifiez la sortie, aucun navigateur n’est nécessaire.

Composabilité. De petits composants s’assemblent pour former de plus grands composants.

La question centrale est passée de "Comment construire cette page ?" à "Quelles composantes forment cette application ?"

4. Next.js : de l’interface utilisateur à l’architecture

React purement côté client avait un inconvénient.

Le navigateur télécharge d’abord une coquille HTML vide. Ensuite, il récupère un bundle JavaScript assez volumineux. Il exécute ensuite ce bundle, puis il récupère les données réelles. Ce n’est qu’après tout cela que l’utilisateur voit du contenu.

Cela fonctionne bien sur un ordinateur portable puissant. Sur un téléphone de gamme avec un signal faible, cela signifie devoir fixer un écran vide pendant plusieurs secondes. De plus, les robots de recherche risquent de ne pas indexer du tout d’informations utiles.

Des frameworks comme Next.js permettent aux développeurs de contrôler où et quand l’HTML est réellement généré.

SSG. Le rendu a lieu au moment de la compilation, et un fichier statique est servi depuis un CDN. C’est l’option la plus rapide, bien que le contenu reste inchangé jusqu’à la prochaine compilation.

ISR. Le contenu reste statique mais se régénère en arrière-plan après une période de temps définie. Vous bénéficiez d’une vitesse similaire à celle des fichiers statiques, avec un contenu restant suffisamment mis à jour.

SSR. Le rendu a lieu sur le serveur pour chaque requête. Le contenu est toujours à jour, mais le serveur effectue des calculs frais à chaque fois.

CSR. Le rendu a lieu entièrement dans le navigateur. Cela convient aux écrans protégés par un authentification, où l’indexation n’a pas d’importance.

L’idée clé : vous choisissez cette méthode pour chaque route, et non pour toute l’application en même temps.

Une page de marketing profite du SSG. Une page de produit profite de l’ISR. Un tableau de bord profite du CSR.

C’est aussi ici que l’hydratation entre en jeu. Le serveur envoie l’HTML, puis le client réexécute l’arbre de composants pour connecter les gestionnaires d’événements. Visuellement, la page semble prête avant même d’être réellement interactive.

React Server Components et le streaming existent principalement pour combler cette lacune. L’HTML est envoyé dès qu’il est disponible, et moins de JavaScript est transmis pour les parties de la page qui n’étaient jamais destinées à être interactives.

À ce stade, le travail frontend cesse d’être simplement « la couche UI ». Les décisions concernant la stratégie de rendu, le routage et le cache deviennent des décisions architecturales, prises par les ingénieurs frontend.

5. Puis vient l’échelle

Construire une application représente déjà un défi. En construire une qui sert des millions d’utilisateurs est un défi complètement différent.

Les questions que l’on se pose commencent à changer :

Que se passe-t-il lorsque une API en aval ralentit au lieu de planter proprement ?

Comment six équipes distinctes peuvent-elles déployer du code dans la même base de code sans se gêner mutuellement ?

Comment déployer une modification uniquement pour 1 % des utilisateurs et la retirer en moins d’une minute en cas de problème ?

Comment l’application se comporte-t-elle sur un téléphone Android vieux de trois ans, dans un endroit où vous ne l’avez jamais testée ?

Quel déploiement d’hier est responsable de l’augmentation soudaine des erreurs d’aujourd’hui ?

Ces sont des questions au niveau du système. Elles apparaissent simplement dans le navigateur.

6. La vitesse n’est pas une touche décorative optionnelle, c’est une attente de base

Une interface élégante qui met six secondes à s’afficher peut tout aussi bien être considérée comme défectueuse. Les utilisateurs perçoivent un retard avant même de pouvoir identifier précisément le problème.

La discipline de performance d’aujourd’hui repose sur des mesures plutôt que sur l’intuition. Les Core Web Vitals fournissent aux équipes un langage commun pour parler de vitesse.

LCP (Largest Contentful Paint) indique le moment où le contenu principal devient visible. Visez un temps inférieur à 2,5 secondes.

INP (Interaction to Next Paint) mesure la rapidité avec laquelle l’interface réagit lorsqu’on touche ou clique dessus. Visez un temps inférieur à 200 millisecondes.

CLS (Cumulative Layout Shift) évalue l’amplitude des déplacements des éléments pendant que la page se stabilise. Visez un score inférieur à 0,1.

Chaque technique d’optimisation correspond à l’une de ces métriques.

Séparation du code et chargement différé. Envoyez uniquement le JavaScript dont la route actuelle a réellement besoin, et non l’ensemble du bundle de l’application. Cela améliore à la fois le LCP et l’INP.

Ajustement des images. Utilisez des formats modernes, ajustez correctement leur taille, et indiquez toujours la largeur et la hauteur au préalable afin que rien ne saute. Cela améliore les indicateurs LCP et CLS.

Gestion intentionnelle des polices. Préchargez-les, appliquez font-display: swap, et restreignez l’ensemble des caractères à ceux qui sont réellement utilisés. Cela permet de résoudre le problème CLS.

Distribution via un CDN. Fournir des données depuis un serveur géographiquement proche de l’utilisateur améliore presque tous les indicateurs en même temps.

Éviter de charger des tâches lourdes sur le thread principal. Le navigateur dispose d’un seul thread qui gère à la fois l’affichage et vos scripts. Une tâche prenant 300 millisecondes bloque tous les clics pendant cette durée. C’est là que se manifestent les avantages de l’INP.

Le principe fondamental, en termes simples :

Fournir la meilleure expérience en utilisant le moins de ressources possible.

Un conseil : ajustez vos paramètres en vous basant sur des mesures réelles, et non sur des suppositions. Les chiffres obtenus depuis l’ordinateur portable d’un développeur en environnement de laboratoire diffèrent souvent de ce que vivent les utilisateurs réels sur le terrain, et c’est bien les données du terrain qui reflètent la réalité.

7. Mémorisation en cache : évitez de recalculer ce qui a déjà été résolu

Au fur et à mesure que le trafic augmente, générer sans cesse la même réponse devient coûteux. La mémorisation en cache existe justement pour éviter de payer deux fois ce coût pour un travail identique.

Chaque requête traverse plusieurs étapes, et chacune représente une opportunité de contourner certaines opérations.

Browser → CDN → Application / ISR → Redis → Database

Plus une réponse est servie près de l’utilisateur, plus elle est économique et rapide.

Cependant, plus les données sont stockées en cache près de l’utilisateur, plus il devient difficile de revenir en arrière si elles sont erronées.

Cette tension définit toute cette pratique.

Qu’est-ce qui doit figurer dans la cache ? Des données stables, partagées entre les utilisateurs et coûteuses à générer. Jamais rien de spécifique à un utilisateur, sauf si la clé de cache tient compte de cet utilisateur en particulier.

Pendant combien de temps doit-elle rester ? Des délais d’expiration courts sont plus sûrs mais apportent moins d’avantages. Des délais longs permettent une vitesse de traitement élevée mais comportent plus de risques.

Comment la vider ? Les options incluent l’expiration basée sur le temps (TTL), les nettoyages déclenchés par des événements (déclenchés lors de la publication), ou l’utilisation de noms de fichiers hachés en fonction du contenu, afin que la validation ne soit même jamais nécessaire.

Que se passe-t-il en cas d’échec de la recherche ? Si une clé largement utilisée expire et qu’un flot de requêtes atteint simultanément le backend, cela provoque une surcharge. Protégez-vous contre cela en utilisant un mécanisme de verrouillage, ou en affichant la version obsolète tandis qu’une seule requête s’occupe de sa mise à jour.

Le modèle à retenir est stale-while-revalidate. Affichez immédiatement la version en mémoire tampon, mettez-la à jour discrètement en arrière-plan, et le visiteur suivant recevra alors du contenu mis à jour. Les utilisateurs bénéficient de vitesse, tandis que vous assurez la correction avec un léger retard.

Faites attention au coût réel d’une erreur dans ce domaine. Un cache défectueux ne ralentit pas seulement les opérations : il peut afficher le mauvais prix, le mauvais nom, ou même des données appartenant à quelqu’un d’autre.

Le cacheage semble être une technique de performance, mais il s’agit en réalité d’une décision liée à la correction des données.

8. Structuration pour la croissance

Lorsque plusieurs équipes travaillent sur une même base de code, sa manière d’être organisée devient tout aussi importante que sa vitesse de fonctionnement.

Limites entre modules. Déterminez ce qui est exposé publiquement, ce qui reste interne, et quelles dépendances sont autorisées.

Contrats pour les API. Typés et versionnés, afin qu’un changement au niveau du backend ne perturbe pas le pipeline de construction plutôt que le site en ligne.

Où se trouve l’état. L’état du serveur, l’état de l’URL et l’état local du client sont des catégories fondamentalement distinctes. La plupart des bugs liés à l’état proviennent du fait de confondre l’une avec l’autre.

Isolement des pannes. Les limites d’erreur garantissent qu’un composant défectueux ne fasse pas tomber toute la page.

Déploiement progressif des changements. Bannières de fonctionnalité, déploiements canari et possibilité de revenir instantanément en arrière.

La mentalité évolue d’un simple fonctionnement d’une fonctionnalité à la garantie que cette fonctionnalité ne rendra pas plus difficiles tous les changements futurs.

9. Code partagé au sein d’un monorepo

Lorsque plusieurs applications dépendent du même en-tête, des mêmes tokens de conception et des mêmes outils d’authentification, copier-coller du code cesse d’être viable.

Cependant, le partage comporte des coûts. Chaque package partagé devient une dépendance, et chaque dépendance constitue en fait une file d’attente : la mise à jour d’une équipe peut retarder la publication d’une autre.

L’objectif réel est la réutilisation sans couplage étroit.

Une heuristique utile : partagez des éléments qui changent rarement et ont toujours le même sens, quel que soit l’endroit où ils sont utilisés — comme les tokens de design, un composant Button ou des définitions de types.

Évitez de partager tout ce qui intègre la logique métier spécifique d’une équipe. Dupliquer du code est souvent moins coûteux que de s’engager envers une abstraction partagée inadaptée.

Les micro-frontends vont encore plus loin en permettant aux équipes de livrer indépendamment, au prix d’une complexité en temps de exécution accrue et de dépendances redondantes. Ce compromis peut être pertinent pour une grande organisation avec des frontières bien définies entre les équipes. En dehors de ce contexte, il rapporte rarement.

10. Surveiller le système en production

À grande échelle, des expressions comme "ça marche sur ma machine" ou "ça fonctionnait bien en environnement de préparation"> perdent beaucoup de leur sens.

Ce dont vous avez réellement besoin, c’est de pouvoir voir comment l’application se comporte en production, sur des appareils réels, en temps réel.

Le suivi des erreurs, associé à des cartes de source téléchargées, permet aux traces d’erreur de faire référence à votre code source réel plutôt qu’à du code compressé et illisible.

La surveillance des utilisateurs réels pour collecter les indicateurs Core Web Vitals auprès d’utilisateurs authentiques, classés par appareil, pays et qualité de connexion.

Le journalisation structurée, liée à un identifiant de requête que vous pouvez suivre depuis le navigateur jusqu’à chaque service backend qu’il interroge.

Des alertes liées aux symptômes. Informez les personnes lorsque le taux d’erreurs ou la latence dépasse un certain seuil, et non simplement lorsque l’utilisation de la CPU d’un serveur augmente brusquement.

Suivi des déploiements. Marquez chaque erreur avec le déploiement spécifique qui l’a introduite, afin que répondre à la question « qu’est-ce qui a changé ? » ne prenne que quelques secondes plutôt que des heures.

Les questions auxquelles cette configuration devrait vous permettre de répondre :

Où exactement les utilisateurs rencontrent-ils des erreurs ? Quelle appel API ralentit l’expérience utilisateur ? Quel chemin est lent, et pour quel segment d’utilisateurs ? Le dernier déploiement a-t-il aggravé la situation par rapport à avant ?

Le code frontend était autrefois la partie la plus difficile du stack à observer. Ce n’est plus nécessairement le cas.

Lectures complémentaires