Architecture en micro-frontends : passer le frontend à l'échelle

Les micro-frontends étendent l'idée des microservices à la couche frontale. Au lieu d'une seule application monolithique, le frontend est découpé en applications plus petites et indépendantes, développées, testées et déployées séparément. Chaque micro-frontend possède un domaine métier distinct et peut être bâti avec d'autres technologies, par d'autres équipes, à d'autres cadences de livraison.

L'architecture est née des mêmes pressions qui ont poussé les microservices côté serveur. À mesure que les applications web gagnaient en complexité, les frontends monolithiques sont devenus difficiles à maintenir, lents à construire et risqués à déployer. Un seul changement, où que ce soit, imposait de reconstruire et redéployer toute l'application, ce qui ralentissait les équipes et créait des frictions entre des groupes qui voulaient avancer à des rythmes différents.

Que sont les micro-frontends ?

Une architecture en micro-frontends découpe une application web en tranches fonctionnelles, chacune appartenant à une équipe autonome. L'équipe répond de toutes les couches de sa tranche : les composants d'interface, la logique métier, la récupération des données et l'intégration avec le serveur. L'utilisateur voit une application unique et cohérente, mais derrière le rideau, elle est composée de plusieurs applications plus petites tournant dans la même fenêtre de navigateur.

Ce découpage suit les principes de la conception pilotée par le domaine. Chaque micro-frontend correspond à un contexte délimité : une frontière logique autour d'une capacité métier précise. Le parcours de paiement, le catalogue de produits, le profil utilisateur et la recherche peuvent être autant de micro-frontends confiés à des équipes différentes.

La différence essentielle entre des micro-frontends et un simple découpage en modules, c'est que les micro-frontends sont indépendants à la construction et au déploiement. Chacun a sa propre chaîne de build, son dépôt, ses tests et son calendrier de livraison. Cette indépendance fait à la fois les bénéfices et les difficultés de l'architecture.

Un micro-frontend n'est ni une bibliothèque de composants ni un ensemble d'utilitaires partagés. C'est une application autonome composée à l'exécution au sein d'une application plus vaste. Cette distinction est la clé pour comprendre à la fois la puissance et le coût de l'architecture.

Module Federation avec Webpack 5

Module Federation est l'approche la plus répandue dans les écosystèmes React et Angular. Introduite avec Webpack 5, elle permet à une application JavaScript de charger à l'exécution du code venu d'une autre application. Le code chargé s'exécute dans le contexte de l'application hôte et partage les dépendances afin de ne pas dupliquer des bibliothèques comme React ou Vue.

Le principe : une application distante expose certains modules, l'application hôte les importe. La distante déclare ce qui est disponible, l'hôte y fait référence comme s'il s'agissait d'imports locaux. Webpack prend en charge le chargement asynchrone, la déduplication des dépendances et la résolution des versions à la construction.

Le partage des dépendances est la caractéristique majeure. Quand hôte et distante utilisent la même bibliothèque, Webpack veille à ce qu'une seule copie soit chargée dans le navigateur. On évite ainsi la dégradation qu'entraîneraient plusieurs copies de React, Lodash ou d'autres grosses bibliothèques. Le motif d'instance unique garantit aussi qu'un état partagé — un magasin Redux, un contexte React — l'est réellement entre micro-frontends.

// webpack.config.js - Remote application exposing a module
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "checkout",
      filename: "remoteEntry.js",
      exposes: {
        "./CheckoutApp": "./src/bootstrap",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
      },
    }),
  ],
};

// webpack.config.js - Host application consuming the remote
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",
      remotes: {
        checkout: "checkout@http://localhost:3001/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
      },
    }),
  ],
};

// Lazy loading the remote module in the host
const CheckoutApp = React.lazy(() => import("checkout/CheckoutApp"));

function Shell() {
  return (
    <Suspense fallback={<Spinner />}>
      <CheckoutApp />
    </Suspense>
  );
}

L'hôte charge le fichier d'entrée distant dès que l'utilisateur atteint une route nécessitant le micro-frontend de paiement. Ce fichier est un petit JavaScript engendré automatiquement par Webpack : il contient l'inventaire des modules exposés et leur emplacement. Quand l'hôte réclame un module précis, Webpack télécharge le fragment correspondant et l'exécute dans le contexte des dépendances partagées.

Point de vigilance : hôte et distante doivent s'accorder sur les versions des dépendances partagées. Si la distante exige React 18.2 alors que l'hôte n'a que React 18.0, le partage en instance unique échouera à moins que les versions ne soient compatibles. Le champ requiredVersion accepte des plages de version sémantique, ce qui laisse de la souplesse tout en protégeant des ruptures.

Intégration par iframe

Avant Module Federation et les techniques modernes, l'iframe était l'approche d'origine. Une iframe intègre dans une page un document HTML entièrement indépendant. Ce document possède son contexte JavaScript, sa portée CSS et son arbre DOM. Cet isolement fait à la fois la force et la faiblesse de la méthode.

L'iframe offre les garanties d'isolement les plus solides. Aucun risque de conflit CSS, puisque chaque iframe a son document. Aucun risque de collision JavaScript, puisque chacune a sa portée globale. Aucun micro-frontend ne peut altérer par mégarde l'état d'un autre, les arbres DOM étant complètement séparés. Une fuite mémoire dans l'un ne peut faire tomber l'autre.

Ces garanties coûtent cher. Les iframes sont lourdes. Chacune charge un document HTML complet, avec toutes ses ressources CSS et JavaScript. Le navigateur traite chacune comme un contexte de navigation distinct, d'où davantage de mémoire, de requêtes réseau et de travail de rendu. Intégrez dix micro-frontends en iframes et le navigateur charge en pratique onze pages.

La communication entre une iframe et sa page parente passe par postMessage, qui est asynchrone et limité aux données sérialisables. Impossible de transmettre des fonctions, des instances de classe ou des références au DOM à travers la frontière. Les iframes conviennent donc mal aux micro-frontends qui exigent une intégration étroite — un formulaire de paiement ayant besoin de l'état du panier de la page parente, par exemple.

L'accessibilité pose aussi problème. Les lecteurs d'écran et autres technologies d'assistance peinent souvent avec des contextes de navigation imbriqués. La navigation au clavier d'une iframe à l'autre manque de cohérence. La recherche dans la page ne franchit pas ces frontières, et l'historique traite chaque navigation interne comme une session à part.

Les iframes conviennent surtout pour intégrer du contenu tiers, là où l'isolement prime et l'intégration reste minimale. Elles sont un mauvais choix pour composer une expérience cohérente où les micro-frontends doivent partager un état, coordonner la navigation et former une surface visuelle unifiée.

Single-spa et autres cadres d'orchestration

Single-spa est un cadre JavaScript qui orchestre plusieurs micro-frontends dans une même page. Il gère leur cycle de vie : montage quand l'utilisateur atteint une route qui les requiert, démontage quand il s'en éloigne, maintien en mémoire des inactifs pour un remontage rapide. Il est indépendant du cadre applicatif et accepte React, Angular, Vue, Svelte et JavaScript sans cadre.

Chaque micro-frontend s'enregistre auprès de single-spa comme une application dotée de fonctions de cycle de vie : bootstrap, mount, unmount et, au choix, update. La racine de single-spa pilote le routage et décide, selon des motifs d'URL, quelles applications sont actives. Quand l'URL correspond à la fonction d'activité d'une application enregistrée, single-spa la monte et démonte celles qui ne le sont plus.

Single-spa règle l'un des problèmes les plus ardus : la cohabitation des cadres. Lorsque deux micro-frontends reposent sur des versions différentes d'un même cadre, ou sur des cadres entièrement distincts, single-spa leur permet de coexister dans la même page sans conflit. Il y parvient en intervenant sur les points d'accroche du cycle de vie et en isolant l'état global de chaque application.

D'autres approches existent : Piral, fondé sur un modèle d'extensions où les micro-frontends sont chargés comme modules depuis un service de distribution, et Open Components, axé sur la composition côté serveur. Il y a aussi la voie des composants web, où chaque micro-frontend est enveloppé dans un élément personnalisé et enregistré auprès de l'interface correspondante du navigateur. Les composants web apportent un cloisonnement natif pour HTML, CSS et JavaScript, et se composent de façon déclarative dans des gabarits HTML.

Le choix dépend de votre pile, de l'organisation de vos équipes et de vos exigences de performance. Module Federation s'impose pour les équipes déjà sous Webpack. Single-spa a du sens dans les architectures mêlant plusieurs cadres. Les composants web conviennent aux organisations qui veulent imposer des frontières indépendantes du cadre. Les iframes ne se justifient que pour l'intégration de contenu tiers.

Bibliothèques de composants et systèmes de conception partagés

Une inquiétude fréquente concerne l'incohérence visuelle. Si chaque équipe bâtit son interface de son côté, l'utilisateur peut croiser des boutons, des typographies et des mises en page différentes en circulant dans l'application. La réponse tient dans une bibliothèque de composants ou un système de conception partagé, consommé par tous les micro-frontends.

La bibliothèque partagée rassemble en général les composants de présentation : boutons, champs, fenêtres modales, éléments de navigation. Ils sont purement visuels et ne portent aucune logique métier. Ils acceptent des propriétés pour les variantes de style, les libellés et les gestionnaires d'événements, mais ne récupèrent pas de données, ne gèrent pas d'état et n'implémentent aucun comportement propre au domaine. Cette séparation garde la bibliothèque stable et l'empêche de devenir un goulot d'étranglement.

Sa gestion de versions demande de la méthode. Publiée comme paquet npm, chaque micro-frontend en épingle une version et se met à jour à son rythme. Cela donne de l'autonomie, mais peut créer une dérive de versions, où le même composant s'affiche différemment d'un micro-frontend à l'autre. Une batterie de tests de régression visuelle sur la bibliothèque aide à repérer ces écarts avant la production.

Autre option : servir le système de conception comme dépendance d'exécution. La bibliothèque est déployée en application autonome et chargée par chaque micro-frontend au démarrage. Tous utilisent alors toujours la dernière version de chaque composant, et la dérive disparaît. En contrepartie, toute mise à jour exige un déploiement et la moindre rupture touche simultanément tous les micro-frontends.

Les jetons de conception comme plus petit dénominateur commun

Les jetons de conception — valeurs nommées pour les couleurs, les espacements, la typographie et les ombres — sont une alternative plus légère qu'une bibliothèque complète. Chaque micro-frontend implémente ses propres composants mais référence le même jeu de jetons. On garde ainsi la liberté de mise en œuvre tout en préservant la cohérence visuelle. Les jetons se diffusent sous forme de propriétés CSS personnalisées, faciles à surcharger et sans aucun outil de construction nécessaire.

Communication entre micro-frontends

Les micro-frontends doivent se parler. Celui du paiement a besoin de savoir ce que l'utilisateur a mis au panier depuis le catalogue. Celui de l'en-tête doit mettre à jour la pastille lorsque celui des notifications en reçoit une nouvelle. Celui de la connexion doit prévenir tous les autres quand l'utilisateur se connecte ou se déconnecte.

Le mécanisme le plus simple est un bus d'événements partagé. Chaque micro-frontend publie des événements sur un canal global et s'abonne à ceux des autres. Le bus se présente le plus souvent comme un émetteur d'événements accroché à l'objet window ou injecté par la coquille. Les événements portent un type et une charge utile ; les abonnés filtrent par type.

// Shared event bus - shell application
// Each micro frontend receives this bus as part of its initialization.

type BusEvent = {
  type: string;
  payload: unknown;
};

type Listener = (event: BusEvent) => void;

class EventBus {
  private listeners: Map<string, Listener[]> = new Map();

  on(type: string, listener: Listener): () => void {
    if (!this.listeners.has(type)) {
      this.listeners.set(type, []);
    }
    this.listeners.get(type)!.push(listener);
    return () => this.off(type, listener);
  }

  off(type: string, listener: Listener): void {
    const listeners = this.listeners.get(type);
    if (listeners) {
      this.listeners.set(
        type,
        listeners.filter((l) => l !== listener)
      );
    }
  }

  emit(type: string, payload: unknown): void {
    this.listeners.get(type)?.forEach((listener) => {
      listener({ type, payload });
    });
  }
}

// Usage in a micro frontend
export function init(bus: EventBus) {
  bus.on("item:added-to-cart", (event) => {
    updateCartBadge(event.payload.quantity);
  });

  bus.emit("user:logged-in", { userId: "abc-123", name: "Jane" });
}

Pour des besoins plus complexes, un magasin d'état partagé vaut souvent mieux qu'un bus d'événements. Un magasin Redux ou Zustand hébergé dans la coquille peut être injecté dans chaque micro-frontend. Chacun y lit l'état transversal et écrit dans son propre magasin local l'état propre à son domaine. Le couplage reste lâche tout en offrant une source unique de vérité pour les données communes : l'utilisateur courant, le contenu du panier, la navigation active.

La couche de communication doit être conçue et documentée explicitement. Les équipes doivent s'accorder sur les noms d'événements, la forme des charges utiles et la frontière entre état partagé et état local. Sans cet accord, les micro-frontends développent des dépendances implicites à l'état interne des autres, et l'on obtient un monolithe distribué, avec toute la complexité des micro-frontends et aucun de leurs bénéfices.

Stratégies de routage

Le routage doit répondre à deux questions : à quel micro-frontend appartient la route courante ? et comment navigue-t-on de l'un à l'autre ? Les réponses dépendent du choix entre un routeur centralisé dans la coquille, une approche distribuée ou une stratégie mixte.

Dans le routage centralisé, une application coquille détient le routeur de premier niveau. Elle définit la carte des routes, détermine quel micro-frontend monter pour chaque motif d'URL et lui transmet les paramètres. Chaque micro-frontend dispose de son routeur interne pour la navigation au sein de son domaine. La coquille gère les passages d'un domaine à l'autre ; les micro-frontends, les cheminements internes.

Dans le routage distribué, chaque micro-frontend possède ses routes. La coquille conserve la correspondance générale entre URL et micro-frontend, mais chacun gère seul ses sous-routes et sa navigation interne. Une bibliothèque de navigation dans la coquille coordonne les changements d'historique et veille à ce que la barre d'adresse reflète l'état de l'application entière, pas seulement du micro-frontend actif.

Dans le routage par événements, le bus coordonne la navigation. Lorsqu'un micro-frontend doit rejoindre une route appartenant à un autre, il émet un événement de navigation. La coquille l'écoute et effectue le déplacement, ce qui provoque le montage de la cible. Les préoccupations de routage restent ainsi hors des micro-frontends et la logique se concentre dans la coquille.

  • Routage centralisé : la coquille détient la carte des routes, les micro-frontends ne gèrent que leur navigation interne. Idéal pour les petites équipes et les frontières simples.
  • Routage distribué : chaque micro-frontend gère ses sous-routes. Idéal pour de grandes équipes aux domaines nettement séparés.
  • Routage par événements : la navigation est coordonnée par un bus partagé. Idéal pour les architectures mêlant plusieurs cadres.
  • Routage hybride : la coquille gère les routes de domaine de premier niveau, les micro-frontends les sous-routes. Idéal pour la plupart des applications en production.

Déploiement et versionnage

L'indépendance de déploiement est la raison première d'adopter les micro-frontends. Chaque équipe doit pouvoir déployer le sien sans se coordonner avec les autres et sans compromettre la stabilité de l'ensemble. Cela suppose de concever soigneusement la chaîne de livraison, la stratégie d'hébergement et le schéma de versions.

Le modèle le plus simple est l'hébergement séparé. Chaque micro-frontend est déployé à sa propre adresse ou dans son propre espace de stockage. La coquille l'y charge à l'exécution. Ce modèle offre une indépendance maximale, chaque équipe maîtrisant son infrastructure, sa chaîne et sa stratégie de retour arrière. La coquille n'a pas besoin d'être redéployée lorsqu'un micro-frontend évolue, puisqu'elle charge dynamiquement la dernière version.

L'hébergement séparé amène une difficulté nouvelle : coordonner les déploiements pour préserver la compatibilité ascendante. Si le micro-frontend de paiement attend une certaine forme de propriétés de la part de la coquille et que le prochain déploiement de celle-ci la modifie, le paiement casse jusqu'à sa propre mise à jour. La parade consiste à versionner le contrat d'intégration — généralement les propriétés transmises par la coquille — et à signaler les ruptures par un versionnage sémantique.

Les déploiements progressifs et les mises en service par paliers sont plus faciles ici, chaque micro-frontend se déployant séparément. Une équipe peut diffuser une nouvelle version à cinq pour cent des utilisateurs, surveiller les taux d'erreur et les indicateurs de performance, puis élargir peu à peu si tout va bien. En cas de problème, elle revient en arrière sur son seul micro-frontend, sans toucher au reste.

Épingler une version depuis la coquille sert de sortie de secours. Si un déploiement introduit une rupture qui a échappé aux tests, la coquille peut épingler la version précédente et rétablir la stabilité pendant que l'équipe corrige. Ce mécanisme prend d'ordinaire la forme d'un fichier de configuration ou d'une variable d'environnement associant chaque micro-frontend à une version déployée précise.

Dépôt unique ou dépôts séparés

La structure des dépôts est un sujet de débat dans la communauté frontale. Les arguments pour le dépôt unique et pour les dépôts séparés ont chacun leur valeur, et le bon choix dépend de la taille des équipes, de la maturité de l'organisation et des préférences d'outillage.

Un dépôt unique regroupe tous les micro-frontends dans un seul dépôt, organisés par répertoires. Chacun a son package.json, sa configuration de construction et son dossier. Cette approche s'appuie sur des outils comme Turborepo, Nx, Lerna ou les espaces de travail pnpm pour gérer les dépendances, lancer les constructions et orchestrer les tâches entre micro-frontends.

  • La configuration d'outillage partagée est simple : un seul ESLint, un seul TypeScript, un seul Prettier pour tous.
  • Les refontes transversales sont plus faciles, le code de tous les micro-frontends étant au même endroit.
  • La gestion des dépendances est centralisée, ce qui limite les décalages de versions.
  • Les commits atomiques touchant plusieurs micro-frontends deviennent possibles lorsqu'un changement concerne plusieurs domaines.

L'approche multi-dépôts place chaque micro-frontend dans son propre dépôt, avec sa chaîne et sa configuration. Les équipes gardent la pleine maîtrise de leur pile et de leur processus de publication. Une équipe qui veut passer de React à Preact peut le faire sans se coordonner, tant que son micro-frontend s'affiche correctement dans la coquille.

  • Les équipes gardent une autonomie totale sur leur flux de travail et leurs outils.
  • La taille du dépôt reste modeste, d'où des clonages rapides et des chaînes efficaces.
  • Aucun outillage particulier de dépôt unique n'est requis : les flux Git habituels et les registres npm suffisent.
  • Une rupture dans un micro-frontend ne peut pas casser la construction d'un autre.

La tendance du secteur penche vers le dépôt unique, surtout dans les organisations qui partagent un système de conception ou des utilitaires. Le coût opérationnel de la coordination entre plusieurs dépôts l'emporte souvent sur le gain d'autonomie, particulièrement pour des équipes déjà proches et dotées de piles voisines. Pour des organisations aux équipes dispersées utilisant des cadres différents, le multi-dépôts reste toutefois préférable.

Questions de performance

Les micro-frontends induisent un surcoût de performance par rapport à une application monolithique. Chacun charge son propre paquet JavaScript, son CSS et, éventuellement, son propre environnement d'exécution de cadre. Le navigateur doit télécharger, analyser et exécuter davantage de code. Le volume transféré augmente et le délai avant que la page soit utilisable au premier chargement s'allonge.

Le partage de dépendances de Module Federation atténue ce surcoût en garantissant qu'une bibliothèque n'est chargée qu'une fois. Mais le code applicatif — composants, utilitaires et logique métier de chaque micro-frontend — n'est pas partagé. Si l'utilisateur traverse trois micro-frontends au cours d'une session, il télécharge le code des trois, même s'il n'en utilise qu'un.

Le découpage du code à l'intérieur de chaque micro-frontend est indispensable. Chacun doit charger paresseusement ses routes, ses composants lourds et ses bibliothèques tierces. Un micro-frontend d'administration contenant une bibliothèque de graphiques ne doit pas la charger tant que l'utilisateur n'atteint pas une page qui en affiche un. C'est la même pratique que dans une application monolithique, appliquée à l'intérieur de chaque frontière.

Le préchargement est une optimisation précieuse. Lorsque l'utilisateur entre sur une route du micro-frontend de paiement, la coquille peut anticiper qu'il ira ensuite vers la confirmation de commande et commencer à récupérer ses ressources en arrière-plan. Cette stratégie doit s'appuyer sur des données réelles d'usage, non sur des suppositions.

Le chemin critique de rendu doit être protégé. La coquille doit s'afficher aussi vite qu'une application monolithique, donc rester légère : peu de JavaScript, peu de CSS, peu de dépendances. Elle répond du premier rendu, et des micro-frontends lents ne doivent pas le retarder. Chacun doit être chargé de façon asynchrone et afficher un état de chargement jusqu'à être prêt.

Les budgets de performance doivent être fixés au niveau de chaque micro-frontend, pas seulement de l'application. Chaque équipe doit connaître la taille maximale de son paquet, le délai maximal avant utilisabilité et l'empreinte mémoire maximale. Tout dépassement devrait faire échouer la chaîne, comme dans une application monolithique.

Tester les micro-frontends

Tester cette architecture suppose trois niveaux : chaque micro-frontend isolé, l'intégration entre eux et l'application composée dans son ensemble. Chaque niveau a ses outils, ses objectifs et ses responsables.

Les tests unitaires et de composants au sein de chaque micro-frontend suivent les mêmes motifs que dans n'importe quelle application frontale. Chaque équipe teste le sien et les tests tournent dans sa propre chaîne. Seule différence : il faut tester en supposant que la coquille et les autres micro-frontends sont peu fiables — vérifier par précaution que le sien se dégrade proprement lorsque la coquille fournit des propriétés inattendues ou qu'un événement n'arrive pas.

L'intégration est le niveau le plus difficile. Le test doit charger la coquille et plusieurs micro-frontends en même temps, rejouer des interactions qui franchissent les frontières et vérifier que le comportement composé est correct. Cypress et Playwright excellent ici, car ils tournent dans un vrai navigateur et manipulent l'application complète.

Une méthode efficace consiste à faire tourner, dans un environnement de test, un déploiement proche de la production : chaque micro-frontend à sa propre adresse, la coquille configurée pour les charger. La batterie s'exécute alors contre ce déploiement et rejoue des parcours réels franchissant les frontières. On révèle ainsi des problèmes invisibles en test isolé : un défaut de synchronisation quand un micro-frontend n'a pas fini de s'initialiser alors qu'un autre lui parle déjà, ou une régression de style quand une modification CSS de l'un déplace la mise en page d'un autre.

Les tests de régression visuelle sont ici particulièrement importants. Le système de conception partagé doit avoir sa propre batterie. Chaque micro-frontend doit avoir la sienne pour ses pages. Et l'application composée doit en avoir pour les parcours critiques. Percy, Chromatic ou Loki s'intègrent à la chaîne et détectent automatiquement les écarts.

Quand NE PAS utiliser les micro-frontends

Les micro-frontends ne conviennent pas à tous les projets. La complexité qu'ils ajoutent — coût opérationnel, coût de performance, besoin de coordination et difficulté de débogage — ne se justifie que par des besoins d'organisation précis. Les plaquer sur un projet qui n'en a pas besoin crée de la friction et ralentit le développement sans contrepartie.

Une petite équipe qui bâtit une seule application n'en a pas besoin. L'architecture vise des organisations comptant plusieurs équipes, chacune responsable d'un domaine distinct. Si votre frontend entier peut être écrit par trois ou quatre personnes, une application monolithique bien découpée en modules sera plus productive, plus rapide à construire et plus facile à entretenir.

Une application aux domaines très couplés est un mauvais candidat. Si chaque fonctionnalité interagit avec toutes les autres — si catalogue, panier et paiement sont si entremêlés qu'on ne peut les séparer proprement —, les découper vous obligera à bâtir des couches de communication complexes qui imitent l'accès direct qu'offrait le monolithe. C'est l'anti-motif du monolithe distribué.

Une organisation sans maturité opérationnelle peinera. L'architecture exige de bonnes pratiques d'exploitation, des tests automatisés, de la supervision et une réponse aux incidents rodée. Chaque équipe doit pouvoir déployer seule et revenir en arrière vite. Si ces bases manquent, mieux vaut les construire d'abord autour d'une application monolithique.

Les applications critiques en performance, aux exigences strictes de temps de chargement, ne toléreront peut-être pas ce surcoût. Si chaque milliseconde compte — par exemple dans un poste de négociation en temps réel ou une interface vidéo —, les requêtes réseau supplémentaires et le temps d'analyse du JavaScript peuvent faire dépasser le budget.

  • Votre équipe compte moins de quatre développeurs frontend.
  • L'application compte moins de cinq frontières de domaine bien distinctes.
  • L'équipe n'a aucune expérience des microservices ou des systèmes distribués.
  • L'organisation n'a ni intégration continue ni supervision automatisées.
  • L'application a des budgets de performance stricts, déjà difficiles à tenir.
  • Les fonctionnalités sont étroitement couplées et ne se séparent pas proprement en domaines.

Les micro-frontends sont une décision d'organisation qui se manifeste en architecture technique. Si vous n'avez pas besoin de l'indépendance qu'ils procurent — équipes distinctes, cadences distinctes, choix techniques distincts —, le surcoût technique n'en vaut pas la peine.

Conclusion

Les micro-frontends sont un motif puissant pour les organisations qui doivent passer le développement frontal à l'échelle sur plusieurs équipes. L'architecture apporte de vrais bénéfices : déploiement indépendant, autonomie des équipes, liberté technologique et possibilité de publier à des rythmes différents. Ces bénéfices sont réels et mesurables lorsque l'architecture est appliquée au bon problème.

La clé du succès est de les considérer d'abord comme une solution d'organisation, ensuite seulement comme une solution technique. L'architecture existe pour rendre les équipes indépendantes, pas pour produire du code élégamment découplé. Chaque décision technique — Module Federation ou iframes, dépôt unique ou multi-dépôts, bus d'événements ou magasin partagé — doit viser à accroître la vitesse des équipes tout en préservant une expérience cohérente.

Commencez petit. Partez d'un seul micro-frontend doté d'une frontière de domaine nette et d'une équipe motivée pour le porter seule. Montrez que la chaîne de livraison fonctionne, que la performance tient et que l'équipe livre plus vite. Puis étendez par paliers. Ce n'est pas tout ou rien : une approche mixte — un ou deux micro-frontends dans une application par ailleurs monolithique — est souvent le meilleur point de départ.

L'échec le plus fréquent est l'abstraction prématurée. Des équipes bâtissent d'élaborées couches d'orchestration, des systèmes d'état partagé et des infrastructures transversales avant de connaître leurs besoins réels. Partez de l'intégration la plus simple possible — une coquille qui charge les micro-frontends via une balise de script ou Module Federation — et n'ajoutez de la complexité que lorsque le simple se révèle insuffisant. Le bon niveau d'abstraction se découvre à l'usage, pas sur le papier.

Module Federation a rendu les micro-frontends plus accessibles que jamais, mais les questions de fond restent organisationnelles. Vos équipes ont-elles besoin de déployer indépendamment ? Votre organisation peut-elle assumer la complexité opérationnelle ? Vos domaines se séparent-ils proprement ? Celles qui répondent oui y trouveront une transformation. Celles qui répondent non y trouveront une source d'agacement. L'architecture est neutre en soi : sa valeur dépend entièrement du contexte dans lequel on l'applique.

See what your own repository can account for.

Thirty minutes on a repository you choose, including the part the record cannot attribute.