Nel 2025, con applicazioni web sempre più complesse, l'architettura micro-frontend è diventata una soluzione matura per scalare sia lo sviluppo che la manutenzione di applicazioni di grandi dimensioni. Esploriamo quando e come implementare questa architettura in modo efficiente.
Fondamenti di micro-frontend
1. Concetti fondamentali
// 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. Architettura tipica
L'architettura è organizzata attorno a un'applicazione contenitore che carica, su richiesta, i micro-frontend di ciascun dominio, ad esempio uno per il marketing, un altro per la dashboard e un altro per autenticazione. Ciascuno di questi micro-frontend utilizza uno strato comune di componenti condivisi, garantendo coerenza visiva e comportamentale senza associare i team.
Quando adottare i microfrontend
1. Indicatori di necessità
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. Analisi dei compromessi
| Aspetto | Vantaggi | Sfide |
|---|---|---|
| Scalabilità | Squadre indipendenti | Complessità iniziale |
| Prestazioni | Ricarica su richiesta | Sovraccarico di esecuzione |
| Manutenzione | Codice isolato | Coerenza tra le app |
| Distribuire | Indipendente | Coordinamento necessario |
| Sviluppo | Autonomia | Curva di apprendimento |
Implementazione con la Federazione dei Moduli
1. Applicazione contenitore
// 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. Applicazioni remote
// 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. Condivisione dello stato
// 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();
Modelli di progettazione
1. Composizione della domanda
// 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. Comunicazione tra app
// 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();
Migliori pratiche
1. Prestazioni
// 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. Test
// 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 };
Sfide e soluzioni
1. Coerenza visiva
// 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. Monitoraggio
// 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(), }); } }
Conclusione
L'adozione di micro-frontend offre vantaggi significativi per progetti scalabili:
- Scalabilità organizzativa: team autonomi e indipendenti
- Flessibilità tecnica: libertà di scegliere le tecnologie
- Distribuzione indipendente: rischi ridotti e maggiore velocità
- Manutenibilità: codice più organizzato e isolato
- Evoluzione graduale: possibilità di migrazione incrementale
Passaggi successivi
- Valuta la reale necessità del tuo progetto
- Iniziare con un progetto pilota in un'area non critica
- Stabilire standard e governance
- Investire in automazione e strumenti
- Monitorare e regolare continuamente
Stai pensando di adottare micro-frontend nel tuo progetto? Condividi le tue domande ed esperienze nei commenti qui sotto!
Leggi anche
- Architettura software scalabile: come costruire sistemi che crescono
- Cache e streaming in Next.js: le prestazioni sono diventate una decisione architetturale
- Scalabilità dell'applicazione: Guida tecnica completa
- Buone pratiche di internazionalizzazione (i18n) in React e Next.js nel 2025
- Microservizi nelle applicazioni: architettura distribuita per dispositivi mobili
- Monolito vs Microservizi: quale architettura scegliere
