I micro-frontend sono diventati uno dei pattern architetturali più dibattuti nello sviluppo frontend. I sostenitori dicono che abilitano deployment indipendenti dei team e scalabilità. I critici dicono che aggiungono complessità inutile. Entrambi hanno ragione — la domanda è quale compromesso conta di più per la vostra situazione specifica.
Ho una prospettiva unica su questo perché sono stato l'architetto principale di un'architettura micro-frontend per la strategia B2C di AXA Svizzera, e ho anche scelto di NON usare i micro-frontend in ruoli successivi. Entrambe le decisioni erano corrette nel loro contesto.
Il problema che i micro-frontend risolvono davvero
I micro-frontend risolvono bene un problema: l'indipendenza organizzativa. Quando avete più team che devono rilasciare funzionalità per la stessa applicazione rivolta agli utenti con tempistiche diverse, e il coordinamento tra questi team è diventato un collo di bottiglia, i micro-frontend possono aiutare.
Tutto qui. Non rendono la vostra app più veloce (di solito il contrario). Non rendono il vostro codice più pulito. Non riducono la complessità. Scambiano complessità tecnica con flessibilità organizzativa.
In AXA avevamo esattamente questo problema. Più team erano responsabili di diverse parti dell'esperienza cliente B2C: preventivi assicurativi, sinistri, gestione polizze e onboarding. Ogni team aveva la propria cadenza di sprint, le proprie priorità e il proprio calendario di rilascio. Un frontend monolitico significava che un bug nel modulo sinistri poteva bloccare un rilascio del modulo preventivi. I team passavano più tempo a coordinare i deployment che a sviluppare funzionalità.
Come l'abbiamo fatto in AXA
Il nostro approccio era pragmatico, non ideologico. Non abbiamo inseguito il sogno dei microservizi di «ogni team sceglie il proprio framework». Abbiamo standardizzato su React e TypeScript — la libertà era nell'indipendenza di deployment, non nella scelta tecnologica.
Ogni modulo era un'applicazione autonoma che poteva essere costruita, testata e deployata indipendentemente. Un'applicazione shell sottile gestiva il routing, l'autenticazione e il layout condiviso. I moduli comunicavano tramite un event bus definito, non tramite import diretti. La differenza chiave rispetto agli approcci runtime: l'aggregazione avveniva al momento del build, ma ogni app poteva comunque deployare individualmente. Questo significava che il team preventivi poteva deployare tre volte al giorno mentre il team sinistri deployava settimanalmente, senza alcun coordinamento.
// Simplified module registration in the shell app
interface MicroFrontendModule {
name: string;
basePath: string;
load: () => Promise<{ mount: (el: HTMLElement) => void }>;
}
const modules: MicroFrontendModule[] = [
{
name: 'quotes',
basePath: '/insurance/quotes',
load: () => import('quotes-module/bootstrap'),
},
{
name: 'claims',
basePath: '/claims',
load: () => import('claims-module/bootstrap'),
},
];
// Each module mounts into its container independently
// Shell handles routing and passes context via eventsI veri costi di cui nessuno parla
Ecco cosa le presentazioni alle conferenze tralasciano:
Lo stato condiviso è difficile. Davvero difficile. Quando due micro-frontend devono condividere il contesto utente, lo stato del carrello o i contatori delle notifiche, state essenzialmente costruendo un sistema distribuito nel browser. Abbiamo passato settimane a sincronizzare lo stato di autenticazione in modo affidabile tra i moduli senza race condition.
La coerenza UX è costosa. Quando i moduli vengono deployati indipendentemente, le incoerenze visive si insinuano. Il modulo A viene rilasciato con stili dei pulsanti aggiornati mentre il modulo B ha ancora quelli vecchi. Abbiamo risolto questo con la nostra libreria di style guide basata su Web Components — componenti agnostici al framework che ogni modulo consumava. Ma costruire e mantenere quella libreria è stato un investimento significativo.
L'overhead di performance è reale. Ogni modulo porta il proprio overhead di runtime. Anche con le dipendenze condivise estratte in un chunk comune, il caricamento iniziale è più pesante di un'applicazione singola. In AXA, il nostro Time to Interactive è aumentato di circa 800ms rispetto al monolite. Abbiamo ottimizzato con lazy loading e prefetching aggressivi, ma l'overhead non è mai scomparso completamente.
L'esperienza degli sviluppatori ne risente. Far girare l'applicazione completa in locale significa orchestrare più server di sviluppo. Il debugging attraverso i confini dei moduli è doloroso. I test di integrazione richiedono che tutti i moduli siano disponibili. Abbiamo costruito un tooling significativo per renderlo praticabile, che era esso stesso un onere di manutenzione.
Quando ho scelto di non usarli
In Migros, costruendo Bikeworld e Micasa, avevamo un problema simile in superficie: più negozi che condividono infrastruttura. L'istinto sarebbe stato di usare i micro-frontend. Ma ho scelto un monorepo PNPM con Turbo invece.
Perché? Perché il problema organizzativo era diverso. Avevamo un team che costruiva entrambi i negozi, non più team che necessitavano deployment indipendenti. I negozi condividevano un design system e parte dell'infrastruttura ma avevano pagine prodotto e flussi di checkout diversi. Un monorepo con package condivisi ci dava il riuso del codice senza la complessità di deployment.
In Vontobel, costruendo una singola piattaforma finanziaria complessa, i micro-frontend sarebbero stati puro overhead. Un team, un target di deployment, un ciclo di rilascio. Un'applicazione Next.js ben strutturata con confini di modulo chiari — imposti da regole di linting e code review, non da isolamento a runtime — era la risposta giusta.
In UBS utilizziamo effettivamente micro-frontend con Webpack Module Federation in 3 team. Abbiamo un monorepo basato su Turborepo, ma i moduli vengono aggregati a runtime — ogni team può deployare indipendentemente. È un approccio diverso da AXA, dove l'aggregazione avveniva al build. Entrambi funzionano, ma la federation a runtime ci dà deployment indipendenti più rapidi mentre il monorepo mantiene consistente il codice condiviso e il tooling.
Il framework decisionale
Dopo aver vissuto con i micro-frontend e scelto alternative, ecco il mio framework per capire quando hanno senso:
Usate i micro-frontend quando: avete 4+ team che rilasciano per la stessa applicazione, il coordinamento dei deployment è diventato un collo di bottiglia misurabile (non solo un fastidio), i team hanno cadenze di rilascio genuinamente diverse, e avete la capacità ingegneristica per costruire e mantenere l'infrastruttura necessaria (shell app, librerie condivise, tooling, pipeline CI/CD).
Non usate i micro-frontend quando: avete meno di 3 team, volete usare framework diversi per modulo (il costo UX è troppo alto), la vostra applicazione ha forti dipendenze di dati tra moduli, non avete capacità dedicata di platform engineering, o li scegliete perché sono di tendenza piuttosto che perché avete sentito il dolore che risolvono.
// Alternatives that solve similar problems with less cost
// 1. Monorepo with package boundaries
// Good for: shared code, independent builds, one team
// pnpm-workspace.yaml + turborepo
// 2. Module-level code splitting in a single app
// Good for: lazy loading, team ownership areas
const AdminModule = lazy(() => import('./modules/admin'));
const QuotesModule = lazy(() => import('./modules/quotes'));
// 3. Feature flags for independent feature releases
// Good for: decoupling deployment from release
if (flags.newCheckout) {
return <NewCheckout />;
}L'architettura è questione di compromessi
La migliore architettura è la più semplice che risolve i vostri problemi reali. Non i problemi che potreste avere tra due anni. Non i problemi che aveva l'azienda che ha scritto quell'articolo su Medium. I vostri problemi, oggi.
I micro-frontend hanno risolto un problema reale in AXA con aggregazione al build. In UBS, la federation a runtime con Webpack Module Federation si adatta a 3 team che lavorano da un monorepo. Un monorepo ha risolto un problema reale in Migros. Un'applicazione singola ben strutturata ha risolto un problema reale in Vontobel. Il pattern non è cambiato — capite prima il problema, poi scegliete la soluzione più semplice che lo affronta.
Se sembra YAGNI applicato all'architettura, è esattamente così. I principi sono gli stessi a ogni livello di astrazione.

