<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Microfrontends mit Angular: Architektur, implementierung und Best practices

Ein Microfrontend wendet das Microservice-Prinzip auf die Präsentationsschicht an: Anstelle einer einzelnen monolithischen Angular-Anwendung ist das Frontend in unabhängige Teile unterteilt (häufig auf separate Produktteams ausgerichtet), jedes mit eigenem Build, eigener Bereitstellung und eigener Pipeline Release-Zyklus, der zusammen mit der Laufzeit oder Build-Zeit zu einer einzigen Benutzererfahrung zusammengefasst wird. Der Vorteil Der Hauptgrund ist nicht technischer, sondern organisatorischer Natur: Verschiedene Teams können autonom veröffentlichen Koordinieren Sie eine gemeinsame monolithische Bereitstellung. Der ebenso reale Kompromiss ist die zusätzliche Komplexität von Komposition, gemeinsamen Abhängigkeiten und Integrationstests – Microfrontends sind nicht die Wahl Die Lösung ist für jedes Projekt geeignet und sollte übernommen werden, wenn die Kosten für die Koordinierung zwischen den Teams bereits den heutigen Wert übersteigen die Kosten der technischen Komplexität, die sie mit sich bringen.

Integrationsmuster: Vergleich

MusterVorteileNachteileWann man es bevorzugt
Client-seitig (Shell + Remotes)Maximale Laufzeitflexibilität, wirklich unabhängige BereitstellungenKomplexität der gemeinsamen Abhängigkeiten, DuplizierungsrisikoMehrere Teams, häufige Releases unabhängig
Serverseitige KompositionNative SEO, kein zusätzliches JS für die KompositionErfordert dedizierte Serverinfrastruktur (SSI/ESI)Öffentlicher Inhalt mit hohem Traffic, SEO-kritisch
Edge-/Reverse-ProxyZentralisierte Zusammensetzung, granularer Cache pro FragmentProxy wird zu einem Single Point of FailureAusgereiftes Infrastrukturteam, starker Bedarf an Edge Caching
WebkomponentenFramework-agnostische, native DOM-KapselungKommunikation zwischen Komponenten weniger natürlich als ein natives FrameworkTeams mit heterogenen Frontend-Stacks (nicht nur Winkel)
iFrameVollständige Isolation, keine CSS/JS-KonflikteSchlechte UX, umständliche Cross-Frame-Kommunikation, keine SEONur für Nicht-Drittanbieter-Integrationsvertrauen ich

Für die meisten Angular-Unternehmensteams ist das clientseitige Muster mit Module Federation bleibt die Standardauswahl: Sie sorgt für ein Gleichgewicht zwischen Laufzeitflexibilität und natürlicher Integration mit Angulars Routing und Abhängigkeitsinjektion, ohne die übermäßige Isolation eines Iframes.

Module Federation (Webpack 5): Shell und Remote

Die Shell (Host) lädt Remote-Bundles zur Laufzeit dynamisch und teilt Abhängigkeiten gängige (Angular, RxJS) als Singletons, um ein mehrfaches Herunterladen zu vermeiden.

// webpack.config.js — SHELL (host)
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        checkout: 'checkout@https://cdn.example.com/checkout/remoteEntry.js',
      },
      shared: { '@angular/core': { singleton: true, strictVersion: true }, 'rxjs': { singleton: true } },
    }),
  ],
};
// webpack.config.js — REMOTE (checkout)
new ModuleFederationPlugin({
  name: 'checkout',
  filename: 'remoteEntry.js',
  exposes: { './Module': './src/app/checkout/checkout.module.ts' },
  shared: { '@angular/core': { singleton: true, strictVersion: true }, 'rxjs': { singleton: true } },
});
// Routing shell — lazy load dinamico del remote
export const routes: Routes = [
  {
    path: 'checkout',
    loadChildren: () => import('checkout/Module').then(m => m.CheckoutModule),
  },
];

Single-SPA: Registrierung und Lebenszyklus

// Registrazione di un microfrontend Angular in single-spa
registerApplication({
  name: 'checkout',
  app: () => import('./checkout.app'),
  activeWhen: ['/checkout'],
});

// Lifecycle esposto dal microfrontend
export const { bootstrap, mount, unmount } = singleSpaAngular({
  bootstrapFunction: singleSpaProps => bootstrapApplication(CheckoutRootComponent),
  template: '',
});

Winkelelemente / Webkomponenten

// Esporre un componente Angular come Web Component standard
const app = await bootstrapApplication(ProductCardComponent);
const element = createCustomElement(ProductCardComponent, { injector: app.injector });
customElements.define('product-card', element);

Obwohl der Verbraucher nicht auf Angular basiert, verwendet er die Komponente wie jedes native HTML-Tag: – das macht Web aus Komponenten sind die richtige Option, wenn die Shell oder andere Mikrofrontends nicht Angular sind.

Leichtere Alternativen: Karte importieren mit dynamischen ES-Modulen (kein Build-Tool). Der Föderation gewidmet, nur natives import() verwies auf versionierte URLs) für Teams, die Ich möchte die Konfigurationskomplexität der Modulföderation und das Muster Nx vermeiden monorepo wenn die Microfrontends ohnehin das gleiche Repository teilen, aber wollen Pflegen Sie unabhängige Build-Pipelines über Affected-Graph.

Dependency-Sharing- und Design-System

Angular und RxJS sollten immer als singleton: true in Module Federation: without geteilt werden Dabei lädt jeder Remote-Zugriff seine eigene Kopie des Frameworks herunter, was das Paket aufbläht und es gefährdet Änderungserkennungsinkompatibilität zwischen Shell und Remote, die zusammen geladen werden.

// package.json della libreria di design system condivisa — peerDependencies, non dependencies
{
  "name": "@azienda/mfe-ui",
  "peerDependencies": { "@angular/core": "^18.0.0", "rxjs": "^7.0.0" }
}

Veröffentlichen Sie Kernkomponenten und Design-Tokens als separate versionierte Bibliothek (nicht in jeder dupliziert). microfrontend), dokumentiert in Storybook – das exakt gleiche Muster wie ein Designsystem zentralisiert, mit dem einzigen Unterschied, dass hier die Verbraucher unabhängige Mikrofrontends sind separate Anwendungen.

Routing, Status und Kommunikation zwischen Microfrontends

Globales Routing (welches Mikrofrontend angezeigt werden soll) liegt weiterhin in der Verantwortung der Shell; Jede Fernbedienung verwaltet ihr eigenes lokales Routing (die internen Unterrouten) in einem Völlig unabhängig – die Shell muss die interne Routing-Struktur von a nicht kennen remote, nur der Eingabepfad.

// Event bus condiviso con RxJS — comunicazione shell/remote senza accoppiamento diretto
export class MfeEventBus {
  private readonly subject = new Subject<{ type: string; payload: unknown }>();
  emit(type: string, payload: unknown) { this.subject.next({ type, payload }); }
  on(type: string) { return this.subject.pipe(filter(e => e.type === type), map(e => e.payload)); }
}

Für den gemeinsamen Status gibt es drei Optionen mit unterschiedlichen Kompromissen: die URL (die einfachste und robust, aber auf den serialisierbaren Zustand beschränkt), ein Ereignisbus wie oben (entkoppelt). (aber ohne eigenen persistenten Zustand) oder ein gemeinsam genutzter Store, der als Bibliothek veröffentlicht wird Singleton (leistungsstärker, führt aber eine implizite Kopplung zwischen Mikrofrontends ein, die übereinstimmen müssen auf einer gemeinsamen Staatsform). Der Vertrag zwischen Shell und Remote muss immer explizit und typisiert sein – Requisiten Eingehende und ausgehende Ereignisse, niemals direkter und undokumentierter Zugriff auf den internen Zustand eines andere Fernbedienung.

CI/CD, Testen und Bereitstellen

Jedes Microfrontend verfügt über eine eigene unabhängige Pipeline: Build, Unit Test, Storybook und Publish Del remoteEntry.js Fingerabdruck im CDN, ohne Abhängigkeit von der Bereitstellung anderer Microfrontends.

# GitHub Actions — sintesi pipeline per un singolo microfrontend
jobs:
  build-and-deploy:
    steps:
      - run: npm ci
      - run: npm run build -- --configuration production
      - run: npm test -- --watch=false
      - run: npx cypress run
      - run: aws s3 cp dist/checkout s3://cdn.example.com/checkout/$GITHUB_SHA --recursive
      - run: node scripts/update-manifest.js checkout $GITHUB_SHA
// scripts/update-manifest.js — aggiorna il manifest che la shell legge per risolvere i remoteEntry
const manifest = JSON.parse(fs.readFileSync('manifest.json'));
manifest[process.argv[2]] = `https://cdn.example.com/${process.argv[2]}/${process.argv[3]}/remoteEntry.js`;
fs.writeFileSync('manifest.json', JSON.stringify(manifest, null, 2));

Für die Integration zwischen Shell und Remote gibt es zwei Teststufen: Vertragstest welche Stellen Sie sicher, dass die Fernbedienung die erwartete Schnittstelle (Requisiten/Ereignisse) bereitstellt, ohne die gesamte Shell bereitzustellen, z E2E gegen eine Staging-Umgebung mit der echten Shell und echten Fernbedienungen, reserviert für kritische Cross-Microfrontend-Flows – Schein-Remotes in Shell-Unit-Tests, verlassen Sie sich nicht nur dazu, um eine echte Integration zu bestätigen.

Leistung und Optimierung

<!-- Prefetch del remoteEntry del prossimo microfrontend probabile, senza bloccare il rendering corrente -->
<link rel="prefetch" href="https://cdn.example.com/checkout/remoteEntry.js" as="script" />
  • Lazy Loading jeder Fernbedienung nur, wenn die entsprechende Route besucht wird, niemals im anfänglichen Shell-Bundle.
  • Fingerabdruck des RemoteEntry (Hash im Pfad, nicht im Dateinamen aus Kompatibilitätsgründen mit Module Federation) für langen Cache ohne versehentliche Ungültigmachungen.
  • Bundle-Analysator für jede Fernbedienung unabhängig, nicht nur auf der aggregierten Shell, um Duplikate von Abhängigkeiten zu erkennen, die nicht korrekt geteilt werden.
  • TTI/LCP gemessen pro einzelnem Mikrofrontend, nicht nur auf der gesamten Seite: Eine langsame Fernbedienung beeinträchtigt die Wahrnehmung der gesamten Shell, selbst wenn der Rest schnell ist.

Sicherheit und Betrieb

# CSP che permette script solo dai domini CDN dei microfrontend fidati
Content-Security-Policy: script-src 'self' https://cdn.example.com; connect-src 'self' https://cdn.example.com;

HTTPS ist auf jedem CDN erforderlich, das einen RemoteEntry bereitstellt, und CORS ist explizit dafür konfiguriert Erlauben Sie der Shell, Cross-Origin-Skripte aus Microfrontend-Domänen zu laden. Verwalten Sie immer die Fehler beim Laden einer Fernbedienung mit einer expliziten Fallback-Komponente, niemals eine leere Seite: Ein nicht reagierendes sekundäres Microfrontend sollte die Nutzung der restlichen Anwendung nicht behindern.

// Fallback route nella shell se un remote non è raggiungibile
loadChildren: () => import('checkout/Module')
  .then(m => m.CheckoutModule)
  .catch(() => import('./fallback/checkout-unavailable.module').then(m => m.CheckoutUnavailableModule)),

Die Überwachung muss für ein einzelnes Microfrontend eingestellt werden, nicht nur für ein Aggregat: Fehlerrate, Bereitstellung Ladehäufigkeit und Latenz von remoteEntry für jedes Team, sodass ein Problem identifiziert wird sofort im zuständigen Microfrontend statt generisch „in der Shell“.

Skalierbarkeit und Rollback

Jede Remote-Bereitstellung wird versioniert (Pfad mit Hash/SHA des Commits im CDN); Rollback besteht aus verweisen Sie das Manifest auf die vorherige Version, ohne dass eine neue Bereitstellung erforderlich ist oder Neuaufbau – der schnellstmögliche Vorgang im Falle einer Regression.

# Rollback: ripunta il manifest alla versione precedente del remote
node scripts/update-manifest.js checkout $PREVIOUS_SHA

Für reibungslose Rollouts verwenden Sie Feature-Flag auf Shell-Ebene (welche Version von Manifest dienen zu welchem Prozentsatz der Benutzer) anstelle eines Kanarienvogels auf Infrastrukturebene mehr Die Orchestrierung einzelner UI-Fragmente ist komplex.

Fallstudie 1: Marktplatz mit 4 Produktteams

Ein Marktplatz mit 4 unabhängigen Teams (Suche, Produkt, Kasse, Konto) hat Modul übernommen Föderation mit zentraler Shell. Erster Einrichtungsaufwand: ~120 Personenstunden für Shell, Manifest und Gemeinsame CI/CD-Pipeline. Nach 5 Monaten: Die Bereitstellungshäufigkeit stieg von 1/Woche (Monolith) auf 12/Woche aggregiert über die 4 Teams, durchschnittliche Rollback-Zeit von 25 Minuten (vollständiger Neuaufbau) auf 40 Sekunden (Manifestaktualisierung), 0 teamübergreifende Zusammenführungskonflikte im Frontend.

Fallstudie 2: SaaS-Plattform expandiert international

Eine SaaS-Plattform mit Teams, die über drei Zeitzonen verteilt sind, hat Webkomponenten eingeführt, um dies zu ermöglichen ein Team mit React-Stack zur Integration in die bestehende Angular-Shell, ohne etwas neu zu schreiben. Aufwand: ~80 Personenstunden für die erste Remote-Webkomponente plus gemeinsam genutzte Token-Designbibliothek. Ergebnis: Die Zeit bis zur Markteinführung des ersten teamübergreifenden Features wurde von 6 auf 3 Wochen verkürzt, keine erzwungenen Abhängigkeiten von einem einzigen Frontend-Stack für das Onboarding neuer Teams.

Checkliste vor dem Start

  • Klare Geschäftsdomänengrenze zwischen Mikrofrontends, teamorientiert, nicht willkürlich.
  • Shared-Design-System wurde vor der ersten Fernbedienung veröffentlicht, nicht danach.
  • Shell-Remote-Vertrag (Requisiten/Ereignisse) explizit dokumentiert und versioniert.
  • Unabhängige CI/CD-Pipeline, die bereits für mindestens die erste Fernbedienung bereit ist, bevor eine zweite hinzugefügt wird.

30/60/90-Tage-Plan

Tage 1-30

  • Shell und erste Remote in der Produktion mit Module Federation – KPI: Build-Erfolgsrate 100 %, unabhängige Bereitstellung verifiziert.

Tage 31-60

  • Zweite und dritte Remote-Onboard-Überwachung pro Mikrofrontend aktiv – KPI: Fehlerrate wird für jede Remote-Einheit separat verfolgt.

Tage 61-90

  • Rollback-getestet End-to-End, Feature-Flags für aktive schrittweise Rollouts – KPI: Rollback-Zeit gemessen unter einer Minute.

FAQ

Dupliziert Module Federation immer Angular, wenn es nicht richtig konfiguriert ist?

Ja, ohne Singleton: true im freigegebenen Abschnitt lädt jeder Remote-Zugriff seine eigene Kopie des Frameworks herunter, wodurch das Paket aufgebläht wird und das Risiko einer Inkompatibilität besteht.

Wie verwalten Sie das Routing, wenn eine Fernbedienung nicht erreichbar ist?

Mit einem expliziten Fallback in der Lazy-Loading-Kette, damit sich der Importfehler niemals als leere Seite ausbreitet.

Brauchen Sie wirklich Nx, um Microfrontends mit Angular zu erstellen?

Nein, es ist nützlich, um mehrere Projekte im selben Repository zu orchestrieren, aber Module Federation funktioniert auch mit völlig separaten Repositorys.

Wie viele Microfrontends sind „zu viele“?

Es gibt keine feste Nummer; Das Warnsignal ist, wenn die Kosten für die Koordinierung zwischen Remote-Verträgen den Nutzen unabhängiger Bereitstellungen übersteigen.

Wie testet man Integrationen zwischen Shell und Remote?

Mit Vertragstests auf einzelnen Remotes plus E2E in einer Staging-Umgebung mit echten Shells und Remotes für kritische Abläufe.

Verlangsamen Mikrofrontends die Leistung im Vergleich zu einem Monolithen?

Kann, wenn nicht mit korrektem Lazy Loading und Prefetch gehandhabt wird; Bei guter Konfiguration sind die Auswirkungen im Vergleich zu den organisatorischen Vorteilen marginal.

Wie teilen Sie Design-Tokens zwischen verschiedenen Mikrofrontends?

Mit einer separat veröffentlichten Bibliothek, versioniert und als Peer-Abhängigkeit von jeder Fernbedienung genutzt.

Webkomponenten oder Modulverbund?

Module Federation, wenn alle Mikrofrontends Angular sind; Webkomponenten, wenn der Stapel heterogen ist oder eine Framework-unabhängige Isolierung erforderlich ist.

Wie kann ich ein einzelnes Microfrontend schnell zurücksetzen?

Neuverweisen des Manifests auf die vorherige Version des RemoteEntry, ohne dass eine neue Bereitstellung erforderlich ist.

Benötigen Sie eine dedizierte Shell oder kann es auch eine Anwendungs-App sein?

Eine dedizierte, minimale und stabile Shell ist vorzuziehen, um das Risiko zu verringern, dass sich ihre erneute Bereitstellung gleichzeitig auf alle Fernbedienungen auswirkt.

Wie vermeidet man CSS-Duplizierung zwischen Microfrontends?

Mit einem gemeinsamen Designsystem als einzige Quelle grundlegender Stile und Kapselung (Shadow DOM oder strenge Namenskonvention) für die spezifischen Stile jeder Fernbedienung.

Sind Microfrontends auch für ein einzelnes Team sinnvoll?

Selten: Der Hauptvorteil ist organisatorischer Natur. Bei einem einzelnen Team überwiegen die Kosten für die Laufzeitkomposition normalerweise den Nutzen.

Häufige Fehler, die es zu vermeiden gilt

  • Angular nicht als Singleton freigegeben: Verursacht Framework-Duplizierung und Änderungserkennungsfehler zwischen Shell und Remote.
  • Kein UX-Fallback für nicht erreichbare Remotes: Verwandeln Sie ein isoliertes Problem in eine leere Seite für die gesamte Anwendung.
  • Undokumentierter Shell-Remote-Vertrag: Jedes Team errät die Schnittstelle, was bei jedem Einsatz zu stillen Regressionen führt.
  • Beliebige Domänengrenzen zwischen Mikrofrontends: Erzeugen Sie übermäßige Cross-Remote-Kommunikation, anstatt die Kopplung zu reduzieren.
  • Dupliziertes Designsystem statt geteilt: Führt innerhalb weniger Wochen zu visuellen Inkonsistenzen zwischen Mikrofrontends.
  • Keine Vertragstests zwischen Shell und Remote: Integrationsregressionen werden nur in der Produktion entdeckt.
  • Unversionierte Remotes bereitstellen: Macht das Rollback genauso langsam und riskant wie eine vollständige Neuerstellung.
  • Nur aggregierte Überwachung auf der Shell: Verhindert, dass Sie schnell erkennen können, welches Microfrontend ein Problem verursacht.
  • iFrame wird für die interne Zusammensetzung zwischen vertrauenswürdigen Teams verwendet: führt zu keiner Kommunikations- und SEO-Komplexität, ohne dass eine vollständige Isolation wirklich erforderlich ist.
  • Kein Vorabrufen wahrscheinlicher Remotes: Bei jeder Navigation zu einem neuen Mikrofrontend werden die vollen Kosten für das Kaltladen übernommen.

So überprüfen Sie

  • Erstellen Sie jede isolierte Fernbedienung: npx webpack --config webpack.config.js und prüfen Sie auf Fehler.
  • E2E-Tests auf echten Shells + Fernbedienungen im Staging: npx cypress run.
  • Manifestüberprüfung: Überprüfen Sie, ob jeder Eintrag auf einen tatsächlich erreichbaren remoteEntry.js verweist (curl -I auf jeder URL).
  • Kontrollieren Sie die Bundle-Größe für jede einzelne Fernbedienung mit einem Bundle-Analysator, nicht nur für die Gesamtschale.
  • Fallback-Test: Deaktivieren Sie vorübergehend eine Fernbedienung und überprüfen Sie, ob die Shell die Fallback-Komponente anstelle eines Fehlers anzeigt.
  • Aktive Überwachung: Stellen Sie sicher, dass Fehlerrate, Bereitstellungshäufigkeit und Latenz für jedes Mikrofrontend separat verfolgt werden.

Fazit

Microfrontends mit Angular lösen ein organisatorisches Problem, nicht primär ein technisches: das Es ist erforderlich, dass mehrere Teams autonom freigeben, ohne einen gemeinsamen Monolithen zu koordinieren. Modul Föderation mit gemeinsamen Abhängigkeiten wie Singletons, einem zentralisierten Designsystem und Verträgen explizit zwischen Shell und Remote und ein versioniertes, manifestbasiertes Rollback sind die Elemente, die Unterscheiden Sie eine wirklich nachhaltige Microfrontend-Architektur von zusätzlicher Komplexität ohne echter Nutzen.

Möchten Sie als Einstieg ein Skelett-Repo oder eine Evaluierung Ihrer Frontend-Architektur? vorhanden? Fordern Sie ein technisches Audit an: In wenigen Stunden der Analyse ist es möglich, das zu identifizieren Korrigieren Sie Domänengrenzen und die wichtigsten Risiken einer Microfrontend-Einführung für Ihr Team.

💬 Leser-Notizen

0 Notizen

Notiz schreiben

Teile deine Meinung, einen Vorschlag oder ein Kompliment

Neueste Notizen

Noch keine Notizen. Sei der Erste, der kommentiert!