Micro-frontends
Arquitetura
Frontend
Escalabilidade
React
Module Federation

Micro-frontends : quand et pourquoi les adopter dans des projets évolutifs

Micro-frontends : quand et pourquoi les adopter dans des projets évolutifs

En 2025, avec des applications Web de plus en plus complexes, l'architecture micro-frontend est devenue une solution mature pour faire évoluer à la fois le développement et la maintenance de grandes applications. Voyons quand et comment implémenter efficacement cette architecture.

Fondamentaux des micro-frontends

1. Concepts de base

// Exemplo de configuração de Module Federation // webpack.config.js const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'container', remotes: { marketing: 'marketing@http://localhost:8081/remoteEntry.js', dashboard: 'dashboard@http://localhost:8082/remoteEntry.js', auth: 'auth@http://localhost:8083/remoteEntry.js', }, shared: ['react', 'react-dom'], }), ], };

2. Architecture typique

L'architecture est organisée autour d'une application conteneur qui charge, à la demande, les micro-frontends de chaque domaine, par exemple un pour le marketing, un autre pour le tableau de bord et un autre pour l'authentification. Chacune de ces micro-frontends consomme une couche commune de composants partagés, garantissant une cohérence visuelle et comportementale sans regrouper les équipes.

Quand adopter les micro-frontends

1. Besoin d'indicateurs

interface ProjectAssessment { teamSize: number; numberOfFeatures: number; deploymentFrequency: number; technicalDebt: { monolithComplexity: number; buildTime: number; testCoverage: number; }; teamAutonomy: { independent: boolean; technicalDecisions: boolean; deploymentControl: boolean; }; } function shouldAdoptMicroFrontends( assessment: ProjectAssessment ): RecommendationReport { const score = calculateAdoptionScore(assessment); return { recommendation: score > 0.7 ? 'Adopt' : 'Evaluate Further', reasons: analyzeFactors(assessment), risks: identifyRisks(assessment), nextSteps: generateRoadmap(assessment), }; }

2. Analyse des compromis

ApparenceAvantagesDéfis
ÉvolutivitéÉquipes indépendantesComplexité initiale
PerformancesChargement à la demandeFrais généraux d'exécution
EntretienCode isoléCohérence entre les applications
DéployerIndépendantCoordination requise
DéveloppementAutonomieCourbe d'apprentissage

Implémentation avec la fédération de modules

1. Application de conteneur

// src/App.tsx import { lazy, Suspense } from 'react'; import { Router, Route, Switch } from 'react-router-dom'; const MarketingApp = lazy(() => import('./remotes/MarketingApp')); const DashboardApp = lazy(() => import('./remotes/DashboardApp')); const AuthApp = lazy(() => import('./remotes/AuthApp')); export default function App() { return ( <Router> <Suspense fallback={<LoadingSpinner />}> <Switch> <Route path="/auth" component={AuthApp} /> <Route path="/dashboard" component={DashboardApp} /> <Route path="/" component={MarketingApp} /> </Switch> </Suspense> </Router> ); } // src/bootstrap.tsx import { createRoot } from 'react-dom/client'; import App from './App'; const mount = (el: HTMLElement) => { const root = createRoot(el); root.render(<App />); }; const devRoot = document.querySelector('#root'); if (devRoot) { mount(devRoot); } export { mount };

2. Applications distantes

// marketing/src/bootstrap.tsx import { createRoot } from 'react-dom/client'; import { createMemoryHistory, createBrowserHistory } from 'history'; import App from './App'; interface MountOptions { defaultHistory?: typeof createMemoryHistory; initialPath?: string; onNavigate?: (location: any) => void; } const mount = ( el: HTMLElement, { defaultHistory, initialPath, onNavigate, }: MountOptions = {} ) => { const history = defaultHistory?.({ initialEntries: [initialPath || '/'], }) || createBrowserHistory(); if (onNavigate) { history.listen(onNavigate); } const root = createRoot(el); root.render(<App history={history} />); return { onParentNavigate({ pathname: nextPathname }) { const { pathname } = history.location; if (pathname !== nextPathname) { history.push(nextPathname); } }, }; }; if (process.env.NODE_ENV === 'development') { const devRoot = document.querySelector('#_marketing-dev-root'); if (devRoot) { mount(devRoot, { defaultHistory: createBrowserHistory }); } } export { mount };

3. Partage d'état

// shared/store/index.ts import create from 'zustand'; interface SharedState { user: User | null; theme: 'light' | 'dark'; setUser: (user: User | null) => void; setTheme: (theme: 'light' | 'dark') => void; } const useSharedStore = create<SharedState>((set) => ({ user: null, theme: 'light', setUser: (user) => set({ user }), setTheme: (theme) => set({ theme }), })); export { useSharedStore }; // shared/events/index.ts type EventCallback = (data: any) => void; class EventBus { private events: Map<string, Set<EventCallback>>; constructor() { this.events = new Map(); } subscribe(event: string, callback: EventCallback) { if (!this.events.has(event)) { this.events.set(event, new Set()); } this.events.get(event)!.add(callback); return () => { this.events.get(event)?.delete(callback); }; } publish(event: string, data: any) { this.events.get(event)?.forEach((callback) => { callback(data); }); } } export const eventBus = new EventBus();

Modèles de conception

1. Composition de la candidature

// container/src/components/MicroFrontend.tsx interface MicroFrontendProps { name: string; host: string; history?: History; } function MicroFrontend({ name, host, history }: MicroFrontendProps) { useEffect(() => { const scriptId = `micro-frontend-script-${name}`; const renderMicroFrontend = () => { // @ts-ignore window[`render${name}`]( `${name}-container`, history ); }; if (document.getElementById(scriptId)) { renderMicroFrontend(); return; } fetch(`${host}/asset-manifest.json`) .then((res) => res.json()) .then((manifest) => { const script = document.createElement('script'); script.id = scriptId; script.crossOrigin = ''; script.src = `${host}${manifest.files['main.js']}`; script.onload = () => { renderMicroFrontend(); }; document.head.appendChild(script); }); return () => { // @ts-ignore window[`unmount${name}`]?.(`${name}-container`); }; }, []); return <div id={`${name}-container`} />; }

2. Communication entre les applications

// shared/communication/index.ts type MessageHandler = (data: any) => void; class MessageBus { private handlers: Map<string, Set<MessageHandler>>; constructor() { this.handlers = new Map(); window.addEventListener('message', (event) => { const { type, data } = event.data; this.handlers.get(type)?.forEach((handler) => { handler(data); }); }); } subscribe(type: string, handler: MessageHandler) { if (!this.handlers.has(type)) { this.handlers.set(type, new Set()); } this.handlers.get(type)!.add(handler); return () => { this.handlers.get(type)?.delete(handler); }; } publish(type: string, data: any) { window.postMessage({ type, data }, '*'); } } export const messageBus = new MessageBus();

## meilleures pratiques

1. Performances

// shared/performance/index.ts interface PerformanceMetrics { ttfb: number; fcp: number; lcp: number; fid: number; cls: number; } class PerformanceMonitor { private metrics: Map<string, PerformanceMetrics>; constructor() { this.metrics = new Map(); this.setupObservers(); } private setupObservers() { new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { this.recordMetric(entry); } }).observe({ entryTypes: ['web-vital'] }); } private recordMetric(entry: PerformanceEntry) { const appName = this.getAppName(); if (!this.metrics.has(appName)) { this.metrics.set(appName, { ttfb: 0, fcp: 0, lcp: 0, fid: 0, cls: 0, }); } const metrics = this.metrics.get(appName)!; metrics[entry.name as keyof PerformanceMetrics] = entry.value; } }

2. Essais

// shared/testing/index.ts import { render } from '@testing-library/react'; import { createMemoryHistory } from 'history'; interface TestConfig { route?: string; initialState?: any; mocks?: any[]; } function renderWithRouter( ui: React.ReactElement, { route = '/', initialState = {}, mocks = [], }: TestConfig = {} ) { const history = createMemoryHistory({ initialEntries: [route], }); return { ...render(ui), history, }; } export { renderWithRouter };

Défis et solutions

1. Cohérence visuelle

// shared/design-system/index.ts import { createTheme, ThemeProvider } from '@mui/material/styles'; const baseTheme = createTheme({ palette: { primary: { main: '#1976d2', }, secondary: { main: '#dc004e', }, }, typography: { fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', }, components: { MuiButton: { styleOverrides: { root: { textTransform: 'none', }, }, }, }, }); export { baseTheme, ThemeProvider };

2. Surveillance

// shared/monitoring/index.ts interface MonitoringConfig { appName: string; version: string; environment: string; } class ApplicationMonitor { constructor(private config: MonitoringConfig) { this.setupErrorBoundary(); this.setupPerformanceMonitoring(); this.setupUserMonitoring(); } private setupErrorBoundary() { window.addEventListener('error', (event) => { this.logError({ type: 'uncaught', error: event.error, location: event.filename, line: event.lineno, column: event.colno, }); }); } private logError(error: any) { console.error('[MFE Error]', { ...error, app: this.config.appName, version: this.config.version, environment: this.config.environment, timestamp: new Date().toISOString(), }); } }

Conclusion

L'adoption de micro-frontends offre des avantages significatifs pour les projets évolutifs :

  1. Évolutivité organisationnelle : Équipes autonomes et indépendantes
  2. Flexibilité technique : Liberté de choisir les technologies
  3. Déploiement indépendant : risques réduits et vitesse accrue
  4. Maintenabilité : code plus organisé et isolé
  5. Évolution progressive : Possibilité de migration incrémentielle

Prochaines étapes

  1. Évaluez le besoin réel de votre projet
  2. Commencez par un pilote dans une zone non critique
  3. Établir des normes et une gouvernance
  4. Investissez dans l'automatisation et les outils
  5. Surveillez et ajustez en permanence

Envisagez-vous d'adopter des micro-frontends dans votre projet ? Partagez vos questions et expériences dans les commentaires ci-dessous !

A lire aussi