Accueil / Articles / Temps d’exécution du navigateur, du serveur ou de la compilation : une carte de décision pour les architectures frontend

Temps d’exécution du navigateur, du serveur ou de la compilation : une carte de décision pour les architectures frontend

Voyez comment SSG, SSR, le streaming, les composants serveur, les BFFs, le rendu en périphérie, les monolithes modulaires et les micro-frontends répondent chacun à une même question : où doit avoir lieu le travail ?

2588 mots

Les discussions sur l’architecture, et en particulier les entretiens pour des postes de haut niveau en frontend, se soucient rarement de ce que vous avez construit. Ils s’intéressent plutôt au pourquoi de votre choix technique, et « c’est ce que l’équipe avait déjà » n’est pas une réponse valable. La bonne nouvelle, c’est que presque tous les modèles d’architecture frontend répondent à la même question fondamentale : quelle partie du travail doit avoir lieu dans le navigateur, quelle autre sur le serveur, et quelle partie au moment de la compilation ? Une fois que vous considérez SSR, les composants serveur, backends-for-frontends et micro-frontends comme différentes façons de tracer cette frontière, choisir parmi elles devient beaucoup plus simple, tout comme justifier ce choix.

Quelques mots sur les sources : les situations décrites ci-dessous proviennent de documents techniques publics et de documentation officielle, auxquels on a attribué la source en conséquence.

Comment la frontière entre client et serveur a évolué

Le web des débuts était simple. Les fichiers HTML statiques se chargeaient presque instantanément et il y avait très peu de choses qui pouvaient tomber en panne.

Puis des frameworks MVC côté serveur tels que Django, Rails et ASP.NET ont commencé à générer de l’HTML à chaque demande. Les pages pouvaient désormais afficher des données dynamiques, mais chaque clic entraînait un rechargement complet de la page.

Les applications monopage développées avec React, Vue ou Angular ont fait pencher la balance dans l’autre direction. Le navigateur a pris en charge le routage, l’état, la validation et parfois même l’authentification. Le résultat est une interface utilisateur qui semble instantanée, mais seulement après avoir été chargée. C’est justement ce critère qui pose problème : de gros bundles JavaScript, un temps de chargement initial lent, ainsi qu’une capacité de découverte limitée. Google exécute bien le JavaScript, mais le rendu peut retarder l’indexation des sites volumineux, et de nombreux autres robots d’indexation, y compris les bots de prévisualisation des réseaux sociaux et de nombreux robots d’IA, n’exécutent jamais de scripts. Le contenu qu’ils ne peuvent pas voir efficacement n’existe pas pour eux.

Vu de loin, l’histoire de l’architecture frontend est une série d’évolutions entre des clients légers, où le serveur effectue la majeure partie du travail, et des clients lourds, où c’est le navigateur qui s’en charge. Chaque modèle présenté dans la suite de ce guide représente une nouvelle position pour cette frontière.

Backend-for-Frontend : redéfinir une API par client

Dans une configuration Backend-for-Frontend (BFF), l’équipe frontend met en place son propre service léger devant les véritables services backend. Ce service a une seule fonction : transformer les données backend en ce dont un seul client a besoin.

Cette approche trouve généralement son origine sur SoundCloud. Vers 2013, alors que l’entreprise divisait un système monolithique en microservices, elle a constaté que les clients web, iOS et Android se disputaient tous une API commune qui ne convenait à aucun d’eux. Chaque client a donc reçu son propre backend dédié et léger. Phil Calçado, qui a participé à cette migration, en a décrit plus tard l’histoire en détail, et l’article de Sam Newman en a fait une description largement citée, donnant ainsi son nom à ce modèle. Netflix est parvenue de manière indépendante à une solution similaire, avec des couches d’adaptation spécifiques aux appareils, car une application de télévision et une application de téléphone nécessitent des données très différentes.

Considérons un cas concret. Une application mobile a besoin du nom du produit, de son prix et d’une miniature. L’application web nécessite en plus des avis, des niveaux de stock et des articles similaires. Un seul point d’entrée partagé envoie soit trop d’informations à la version mobile, soit trop peu à celle web, ce qui pousse les équipes à ajouter des paramètres de requête pour compenser.

Un BFF résout ce problème en fournissant à chaque interface une réponse adaptée. Le service Express présent ci-dessous, qui utilise la fonction fetch intégrée à Node 18 et versions ultérieures, appelle l’API du produit en amont et ne renvoie que quatre champs, en remplaçant title par name et en réduisant la liste d’images à une seule miniature :

// bff.js — Node 18+, fetch is built in
import express from 'express';

const app = express();
const API = 'https://api.example.com';

app.get('/products/:id', async (req, res) => {
  const response = await fetch(`${API}/products/${req.params.id}`);
  const data = await response.json();

  res.json({
    id: data.id,
    name: data.title,
    price: data.price,
    thumbnail: data.images[0],
  });
});

app.listen(4000, () => console.log('BFF listening on :4000'));

Le client demande /products/123 et reçoit précisément la structure souhaitée, sans champs supplémentaires ni allers-retours inutiles. Dans du code en production, on vérifierait également response.ok avant le parsing, et on protégerait contre les produits ne possédant pas d’images, car data.images[0] suppose qu’il y en a au moins une.

Ce coût mérite plus d’attention qu’il n’en reçoit généralement. Un BFF représente un service supplémentaire à déployer, à surveiller et à maintenir en bon état. S’il tombe en panne, l’interface utilisateur échoue avec lui, même lorsque le vrai backend fonctionne parfaitement. Il ne s’agit pas d’une infrastructure gratuite ; c’est un point de défaillance supplémentaire dont l’avantage principal est d’offrir une vie plus confortable à l’équipe frontend. Pour en savoir davantage sur sa création au sein d’une application Next.js, consultez transformer les gestionnaires de routes Next.js en une couche BFF délibérée.

Stratégies de rendu : où est généré l’HTML

Ces dernières années, le rendu a évolué plus que tout autre domaine, et en pratique les catégories se confondent davantage que ne le suggèrent les diagrammes.

Génération statique et régénération incrémentale

La génération de sites statiques (SSG) rend chaque page au moment de la compilation et sert des fichiers simples depuis un CDN. Rien n’est plus rapide ou moins coûteux à servir, mais le contenu reste inchangé jusqu’à la prochaine mise en production.

La régénération statique incrémentale (ISR) offre une solution de déblocage : une page peut se reconstituer en arrière-plan après un intervalle prédéfini. Vous conservez la plupart des avantages des fichiers statiques sans avoir à rémettre en production chaque fois que le contenu change.

Rendu côté serveur et streaming

Le rendu du côté serveur (SSR) génère de l’HTML pour chaque demande. Dans sa forme classique, le serveur envoie la page complète, puis le navigateur la hydrate : il télécharge le bundle JavaScript et ajoute des gestionnaires d’événements ainsi qu’un état au markup déjà affiché à l’écran.

React 18 a introduit le SSR en flux via renderToPipeableStream, qui envoie des fragments d’HTML dès que chaque partie de l’arborescence est prête, au lieu d’attendre la composante la plus lente. Le flux est le choix habituel pour les nouvelles applications, bien que de nombreuses applications en production continuent de fonctionner sans problème avec le SSR classique qui traite tout d’un coup.

Composants serveur, îlots et capacité de reprise

React Server Components (RSC) et les architectures îlotées vont encore plus loin : seules les parties interactives d’une page font transmettre du JavaScript. Le contenu statique reste de l’HTML pur, sans rien à hydrater. les îlots d’Astro appliquent cette idée en dehors de React. Qwik va encore plus loin avec la reprise possible, qui évite largement l’hydratation en serialisant l’état de l’application dans l’HTML afin que le client puisse reprendre là où le serveur s’est arrêté.

L’exemple ci-dessous montre une page de composant serveur dans le Next.js App Router. À partir de Next.js 15, params est une Promise qui doit être attendue, c’est pourquoi le composant déstructure id uniquement après await params. La récupération des données s’effectue sur le serveur, de sorte que le nom du produit et son prix arrivent sous forme d’HTML déjà prêt à être affiché :

// app/products/[id]/page.tsx (Next.js 15+)
import { fetchProduct } from '@/lib/api';

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetchProduct(id); // runs on the server

  return (
    <main>
      <h1>{product.name}</h1>
      <p>${product.price}</p>
      <AddToCartButton productId={product.id} />
    </main>
  );
}

Seul AddToCartButton inclut du JavaScript côté client. Pour que cela fonctionne, il doit se trouver dans son propre fichier marqué de la directive 'use client' et être importé dans la page ; l’extrait omet cet import par concision. Le résultat est un petit bundle qui reste interactif là où l’interaction est nécessaire, et statique partout ailleurs.

Rendu Edge et raison pour laquelle l’industrie a en partie changé de direction

Le rendu en périphérie est l’un des exemples les plus évidents dans le développement frontend moderne d’une idée testée en public et révisée au fil du temps.

Entre 2021 et 2023 environ, l’argument était convaincant : exécuter le SSR en périphérie, sur des plateformes telles que Cloudflare Workers ou Vercel Edge Functions, dans un centre de données proche de chaque utilisateur. Moins de distance, pages plus rapides. Vercel a fortement promu cette approche.

Vercel a par la suite revu ouvertement sa position ; son alors vice-président du produit l’a résumée en disant « celui-ci m’a eu ». La raison est édifiante. Le traitement des données doit se faire près de l’utilisateur, mais il doit également être proche des données elles-mêmes, or la plupart des bases de données se trouvent dans une seule région. Une fonction d’edge à Tokyo qui effectue plusieurs allers-retours vers une base de données en Virginie est souvent plus lente que le simple rendu en Virginie. Lorsque Vercel a mesuré cela sur son propre produit, v0, le rendu simple avec Node.js s’est avéré supérieur au rendu via des fonctions d’edge. Vercel a depuis abandonné les fonctions d’edge indépendantes et recommande désormais l’environnement de exécution Node.js, avec le traitement des données placé dans la même région ; consultez la documentation actuelle de la plateforme pour connaître le statut exact de chaque environnement de exécution.

Ce qui a survécu, c’est une idée plus restreinte : fournir immédiatement l’enveloppe statique d’une page depuis le edge, puis diffuser les parties dynamiques provenant de serveurs situés à proximité des données. C’est globalement ce que fait Partial Prerendering ; l’explication du pré-rendu partiel et du rendu concurrent décrit les mécanismes en détail.

Cloudflare Workers reste une véritable plateforme SSR edge et fonctionne bien lorsque les données elles-mêmes sont distribuées à l’échelle mondiale. La leçon importante est que la localisation des données l’emporte généralement sur la localisation de l’utilisateur. Comprendre pourquoi le secteur a changé d’avis vaut plus que de connaître simplement ce terme à la mode.

Le monolithe frontend modulaire

Lorsqu’une application monopage dépasse le nombre restreint d’équipes qui la développent, un répertoire unique devient risqué. Tout le monde modifie les mêmes composants partagés, et personne ne sait exactement qui est responsable de quoi.

monolithe modulaire sépare la base de code en deux couches tout en conservant une seule couche exploitable :

  • Une couche de plateforme, gérée par une équipe dédiée, contenant le système de conception, les fonctionnalités partagées, le journalisation et d’autres infrastructures similaires.
  • Une couche de domaine composée de dossiers correspondant aux fonctionnalités telles que user/ ou payments/, chacun étant géré par une équipe spécifique.

La motivation est similaire à celle de l’architecture propre ou hexagonale, mais sans la plupart des formalités associées. Une architecture propre complète est généralement excessive pour le frontend ; un bouton et une requête d’obtention de données n’ont pas besoin de trois niveaux d’abstraction entre eux. Ce qui compte, c’est une responsabilité claire et des limites strictement respectées, par exemple des règles de vérification qui empêchent un domaine d’importer les éléments internes d’un autre.

Micro-frontends : indépendance à un prix

L’architecture des micro-frontends traite chaque domaine comme une mini-application pouvant être déployée séparément, généralement chargée en temps de exécution par une application principale au moyen d’un mécanisme tel que Webpack Module Federation.

Ce que l’on gagne, c’est une véritable autonomie : les équipes peuvent publier selon leurs propres calendriers et, si c’est vraiment nécessaire, utiliser même des frameworks différents. Zalando, IKEA et DAZN ont toutes décrit leur expérience de mise en œuvre à grande échelle, toujours avec de grandes organisations d’ingénierie et des investissements importants dans des outils partagés. micro-frontends.org reste la référence standard pour tout savoir sur ce sujet.

Le mode de défaillance qui cause réellement des problèmes

Le problème qui revient constamment dans les rapports d’incidents réels n’est pas le mélange de frameworks. Il s’agit de la dérive des dépendances partagées. Une application distante met à jour une bibliothèque partagée tandis qu’une autre ne le fait pas, et soudainement deux copies de React tournent sur la même page en se disputant le même DOM. Module Federation peut déclarer des singulaires partagés et des plages de versions pour éviter cela, mais uniquement si les équipes s’accordent sur ces contraintes et les appliquent. C’est ce problème de coordination précis qui épuise les équipes, bien plus que la « complexité » vague généralement invoquée.

Il existe également un exemple d’avertissement dans la direction opposée. Spotify aurait expérimenté une approche de micro-frontend basée sur des iframe dans son client desktop il y a plusieurs années, avant de revenir à une architecture unifiée, en partie parce que les interfaces entre les différentes composantes coûtaient plus cher que l’indépendance qu’elles offraient. Même à grande échelle, ce modèle n’est pas systématiquement une solution gagnante.

Laissez la taille de l’équipe guider la décision

La question qui apparaît rarement sur les diagrammes d’architecture est le nombre exact d’ingénieurs que vous disposez. Les fourchettes suivantes sont des heuristiques issues de la manière dont les équipes décrivent généralement leur décision par la suite, et non des règles strictes :

  • un monolithe modulaire gagne presque toujours. Il n’y a pas assez de personnes pour justifier des pipelines de déploiement distincts.
  • Environ 15 à 50 ingénieurs avec quelques limites de domaine claires : des BFF associés à un monolithe modulaire bien organisé suffisent généralement. Les micro-frontends sont probablement encore prématurés.
  • les micro-frontends commencent à se révéler utiles, non pas parce que l’application a grandi, mais parce que l’organisation l’a fait.
  • Expliquer de manière convaincante un choix architectural

    Les postes de haut niveau attendent une décision, pas un catalogue d’options. Une réponse solide comporte généralement quatre parties :

    1. Ce que vous mettez en œuvre. Par exemple : les pages de marketing sont générées statiquement, les tableaux de bord utilisent le SSR, et un BFF se trouve devant l’application mobile.
  • Pourquoi. La génération statique permet une affichage très rapide pour des contenus qui changent rarement ; le SSR permet l’affichage de données personnalisées, comme le nom de l’utilisateur, sans qu’apparaisse du contenu incorrect.
  • Le coût que vous avez choisi d’assumer. Le BFF introduit une étape supplémentaire, mais il donne à l’équipe frontend le contrôle de son contrat de données, ce qui en justifie l’utilisation.
  • Ce que vous avez rejeté et pourquoi. Les micro-frontends ont été évalués, mais la charge de coordination ne s’est pas avérée justifiée avec la taille actuelle de l’équipe.
  • Le dernier point est le plus important. Expliquer ce que vous avez délibérément choisi de ne pas adopter est ce qui distingue la simple description d’un schéma de la prise d’une décision réelle. Le changement de stratégie en matière de rendu aux limites en est une illustration parfaite : même la plateforme qui défendait cette approche a modifié son orientation lorsque les mesures obtenues étaient contradictoires.

    Questions fréquentes

    En quoi SSR et SSG diffèrent-ils ?

    Puisque SSR rend la page à chaque demande, il peut intégrer des données en temps réel ou spécifiques à l’utilisateur. SSG génère une fois l’HTML lors de la compilation et sert des fichiers statiques, ce qui est plus rapide et moins coûteux, mais seulement aussi à jour que la dernière déploiement. ISR se situe entre les deux en régénérant les pages individuelles selon un calendrier prédéfini.

    Vaut-il la peine d’utiliser un BFF avec un seul frontend ?

    Généralement non. Un BFF trouve son utilité lorsque plusieurs clients, tels que les appareils mobiles, le site web et une API partenaire, ont besoin de charges utiles considérablement différentes provenant d’un même backend. Avec un seul frontend, il ajoute principalement une étape réseau et un service supplémentaire à gérer.

    Le rendu en edge est-il mort ?

    Non, mais la valeur par défaut a changé. Fournir des shells statiques depuis le edge reste utile, et les plateformes edge comme Cloudflare Workers se distinguent lorsque les données sont distribuées à l’échelle mondiale. Pour les applications typiques alimentées par une base de données unique dans une région, la règle d’or est désormais de placer le traitement informatique près des données plutôt que près de l’utilisateur.

    Quand une équipe devrait-elle passer aux micro-frontends ?

    Lorsque la coordination des mises en production entre équipes devient le véritable goulot d’étranglement, et non avant. Une application complexe gérée par une seule équipe bénéficie des mêmes avantages organisationnels qu’un monolithe modulaire, mais avec une fraction seulement des coûts supplémentaires.

    Points clés

    • Chaque modèle présenté ici répond à une seule question : quelle partie du travail revient au navigateur, au serveur ou à l’étape de compilation.
  • Le choix de la solution idéale dépend davantage de la taille de l’équipe, du lieu où se trouvent les données et de la difficulté des déploiements, que du fait de savoir quel modèle semble le plus impressionnant.
  • Les BFF, les micro-frontends et le rendu en périphérie exigent tous un sacrifice en termes de coûts opérationnels pour obtenir un avantage spécifique ; précisez clairement ce compromis.
  • Ayez toujours à portée de main les raisons pour lesquelles certaines options ont été rejetées. Ce sont les preuves les plus convaincantes que le choix a été fait délibérément.