Il mio viaggio nel mondo dei microfrontend
Il mio viaggio nel mondo dei microfrontend è iniziato all’interno delle Gilde: spazi di confronto e sperimentazione all’interno delle quali le persone possono condividere competenze, discutere nuove soluzioni architetturali e approfondire tecnologie emergenti.
Proprio durante la Gilda i3Skillz, avvenuta nel terzo quadrimestre del 2024, ho avuto modo di avvicinarmi per la prima volta al paradigma dei microfrontend, analizzandone, insieme ai colleghi compagni di percorso, vantaggi, sfide e le possibili applicazioni nei nostri progetti reali.
Poco tempo dopo la fine della Gilda ho iniziato a lavorare per un progetto che mi ha dato l’opportunità di utilizzare i microfrontend, permettendomi quindi di comprenderne meglio il loro vero potenziale e come usarli nel modo migliore.
In questo articolo vi porterò il mio punto di vista sui microfrontend: che cosa sono, quando e come usarli (Module Federation e Native Federation) con i relativi pro e contro, e infine un piccolo esempio pratico di un’architettura composta due microfrontend Angular.
Microfrontend: che cosa sono?
Il microfrontend è uno stile architetturale di sviluppo di applicazioni Web che viene applicato appunto a livello di frontend. Si applica sostanzialmente lo stesso concetto dei microservizi lato backend: invece di avere un unico grande monolite, l’interfaccia dell’applicazione viene suddivisa in parti differenti, ciascuna indipendente dalle altre e con un compito ben definito.
Ogni microfrontend è quindi un’intera applicazione indipendente dalle altre, che “espone” all’esterno le sue funzionalità e può quindi:
- essere sviluppato e rilasciato con una propria pipeline;
- utilizzare tecnologia e framework diversi dagli altri microfrontend (se la tecnologia utilizzata lo supporta);
- evolvere senza bloccare gli altri microfrontend.
Microfrontend: quando utilizzarli?
I microfrontend non rappresentano sempre la soluzione ideale per i progetti, anzi: spesso vengono usati perché “vanno di moda”, ma con una complessità architetturale (e quindi di manutenzione della codebase) che non ripaga lo sforzo di sviluppo impiegato per implementarli.
I microfrontend possono tornare utili quando:
- la piattaforma è grande e in continua evoluzione;
- più team lavorano sullo stesso frontend in compartimenti “stagni” gli uni dagli altri;
- servono rilasci indipendenti e frequenti;
- si vogliono sperimentare tecnologie o librerie diverse, senza riscrivere tutto il codice;
- si hanno delle vere e proprie aree che possono essere indipendenti.
Di contro, non mi sento di consigliare l’utilizzo dei microfrontend quando:
- il progetto è piccolo o si hanno a disposizione pochi sviluppatori;
- non ci sono esigenze di rilascio indipendenti;
- l’applicazione ha molti componenti con logiche “connesse” ad altri.
Un caso d’uso tipico
Un esempio pratico che può farci capire meglio quando ci possono tornare utili i microfrontend è per lo sviluppo di un portale di e-commerce composto da diverse sezioni:
- un catalogo prodotti, con l’elenco di tutti i prodotti divisi per cateogorie;
- il carrello, che mostra il riepilogo dei prodotti che si intendono acquistare;
- la fase di checkout, con le sue integrazioni con i metodi di pagamento.
In questo caso ha senso pensare di suddividere l’applicazione in diversi compartimenti stagni e quindi l’uso dei microfrontend potrebbe tornare utile.
Come utilizzare i microfrontend: Module Federation e Native Federation
Arrivando alla parte tecnica, esistono diverse strategie per far convivere più microfrontend nella stessa applicazione.
Per poter implementare queste tecnologie è necessaria un’appicazione che funge da “orchestratore” dei microfrontend ovvero decidere quale microfrontend deve essere eseguito in un determinato punto dell’applicazione.
Ma come si applicano tecnicamente i microfronted? Esistono diverse strategie per far convivere più microfrontend nella stessa applicazione, le due più utilizzate oggi sono Module Federation e Native Federation.
Module Federation
Questa è attualmente la soluzione più diffusa, basata su webpack. L’obiettivo è esporre moduli applicativi verso l’esterno, i quali possono essere integrati nella “shell” dell’applicativo.
Vantaggi
- Estrema flessibilità.
- Supporto della condivisione di librerie tra framework differenti (React, Angular, ecc.).
- Possibilità di rilascio dei microfrontend in completa autonomia.
Svantaggi
- Module Federation è basato su Webpack, quindi serve un’integrazione specifica qualora si volessero usare altre tecnologie come Vite, esbuild o altri bundler.
- Per progetti di grandi dimensioni, la configurazione può diventare complessa.
Native Federation
È un’evoluzione del concetto di Module Federation, senza vincolo sull’uso di webpack. Native Federation si basa sugli standard del browser, in particolare ES modules (ECMAScript) e import dinamici.
Vantaggi
- Nessuna dipendenza da bundler specifici.
- “Future-proof”, ovvero Native Federation utilizza direttamente le primitive del browser senza dover tener conto di webpack.
- Perfetto per architetture ibride e microfrontend molto indipendenti.
Svantaggi
- Ecosistema meno maturo rispetto a webpack.
- Scarsa documentazione e community.
- Alcune ottimizzazioni (come la condivisione automatica delle dipendenze) richiedono lavoro aggiuntivo.
Implementazione di una semplice architettura a microfrontend
Vi mostro ora quanto sia semplice mettere in piedi un’architettura a microfrontend.
Riprendendo il lavoro svolto nella Gilda i3Skillz, ho scelto di utilizzare Angular per realizzare due microfrontend che saranno renderizzati all’interno della stessa pagina. La shell avrà quindi una pagina che ospita i due microfronted.
Gestione del file app.component.ts
Di default Angular inizia la sua esecuzione con il file app.component.ts all’interno del quale ho integrato tutta la logica di cui ho bisogno per far apparire i due microfrontend.
import {Component, OnInit, signal} from '@angular/core';
import {RouterOutlet} from '@angular/router';
import {loadRemoteModule} from '@angular-architects/native-federation';
import {NgComponentOutlet} from '@angular/common';
@Component({
selector: 'app-root',
imports: [RouterOutlet, NgComponentOutlet],
template: `
<main class="main">
<h1>Shell Application</h1>
<div style="display: flex; gap: 20px; margin-top: 20px;">
@if (mfe1Component()) {
<div style="flex: 1;">
<ng-container *ngComponentOutlet="mfe1Component()"></ng-container>
</div>
}
@if (mfe2Component()) {
<div style="flex: 1;">
<ng-container *ngComponentOutlet="mfe2Component()"></ng-container>
</div>
}
</div>
<router-outlet></router-outlet>
</main>`,
})
export class AppComponent implements OnInit {
mfe1Component = signal<any | undefined>(undefined);
mfe2Component = signal<any | undefined>(undefined);
protected readonly title = signal('shell-native-federation');
ngOnInit() {
loadRemoteModule('mfe1', './Component').then(m1 => {
this.mfe1Component.set(m1.Mfe1Component);
});
loadRemoteModule('mfe2', './Component').then(m2 => {
this.mfe2Component.set(m2.Mfe2Component);
});
}
}
Ho utilizzato la funzione loadRemoteModule messa a disposizione della libreria @angular-architects/native-federation – una libreria che integra Native Federation per Angular – la quale importa dai due microfrontend remoti mfe1 e mfe2 il modulo chiamato Component.
Per esportare i due componenti sopraccitati ho utilizzato il medesimo approccio, mostrerò quindi quello usato per mfe1.
Microfrontend mfe1
Per esportare i componenti va aggiunta la libreria @angular-architects/native-federation all’applicazione e, nel file di configurazione della federation federation.config.js, si deve portare fuori il componente in questione.
const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
name: 'mfe-1-native-federation',
exposes: {
'./Component': './src/app/components/mfe1/mfe1.component.ts',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
skip: [
'rxjs/ajax',
'rxjs/fetch',
'rxjs/testing',
'rxjs/webSocket',
],
features: {
ignoreUnusedDeps: true
}
});
Di seguito riporto il codice per esportare con il nome ./Component il componente mfe1 dell’applicazione.
import {Component} from '@angular/core';
@Component({
selector: 'app-mfe1',
imports: [],
template: `
<div class="mfe-container">
<h2>MFE 1</h2>
<p>This is the MFE 1 component.</p>
</div>
`,
styles: `
.mfe-container {
border: 5px solid red;
padding: 20px;
background-color: #ffe6e6;
}
`,
})
export class Mfe1Component {
}
Il componente è molto semplice, renderizza nel suo html un semplice div con del testo per far distinguere mfe1 da mfe2, la logica di esportazione quindi è solamente all’interno del file manifest precedentemente trattato.
Il metodo inifFederation e il file manifest
Dopo aver esportato i due microfrontend è bene verificare che le applicazioni vengano eseguite su porte differenti: ho quindi impostato mfe1 sulla porta 4201 e mfe2 sulla porta 4202.
Fatto questo bisogna modificare il file main.ts della shell, per far si che esegua correttamente la federation.
import { initFederation } from '@angular-architects/native-federation';
initFederation('federation.manifest.json')
.catch(err => console.error(err))
.then(_ => import('./bootstrap'))
.catch(err => console.error(err));
Tramite la funzione initFederation, che prende in ingresso il file manifest, comunico quali porte utilizzeranno i microfrontend.
Per gestire eventuali cambi di ambiente di esecuzione, ho optato per un file .json, semplice da modificare.
Per concludere, vi riporto quello che sarà il contenuto del file manifest, che conterrà solamente la coppia nome-location del nostro microfrontend.
{
"mfe1": "http://localhost:4201/remoteEntry.json",
"mfe2": "http://localhost:4202/remoteEntry.json"
}
L’applicazione e i due microfrontend in esecuzione
Tutto è pronto, non ci resta che avviare le tre applicazioni.
Andiamo all’indirizzo della shell – nel mio caso http://localhost:4200 – per vedere appunto la shell con i due microfrontend.

Conclusioni
Il mondo dei microfrontend non rappresenta solamente una scelta tecnica, ma anche organizzativa. È un approccio preferibile alla classica architettura monolitica quando si ha la consapevolezza che l’applicazione da sviluppare abbia dei compartimenti stagni, senza molte logiche condivise tra gli stessi. Così facendo si può ipotizzare anche di organizzare i team di sviluppo in maniera tale che lavorino a contemporaneamente a componenti diversi, parallelizzando quindi il lavoro.
Se invece si dovesse avere la consapevolezza che l’applicazione da sviluppare ha molte logiche connesse, sarebbe da preferire una architettura monolitica, così da non snaturare e dover “complicare” l’architettura a microfrontend.