Luca Mele
Luca Mele

Architecture

Micro-Frontends: Wann sie Sinn machen (und wann nicht)

Micro-Frontends: Wann sie Sinn machen (und wann nicht)
Zurück zu allen Beiträgen
·8 Min. Lesezeit
KI-generierten Podcast anhören
Alle Podcasts →

Micro-Frontends sind zu einem der meistdiskutierten Architekturmuster in der Frontend-Entwicklung geworden. Befürworter sagen, sie ermöglichen unabhängige Team-Deployments und Skalierung. Kritiker sagen, sie fügen unnötige Komplexität hinzu. Beide haben recht — die Frage ist, welcher Kompromiss für Ihre spezifische Situation wichtiger ist.

Ich habe eine einzigartige Perspektive darauf, weil ich der leitende Architekt einer Micro-Frontend-Architektur für die B2C-Strategie der AXA Schweiz war und mich in späteren Rollen bewusst gegen Micro-Frontends entschieden habe. Beide Entscheidungen waren im jeweiligen Kontext richtig.

Das Problem, das Micro-Frontends tatsächlich lösen

Micro-Frontends lösen ein Problem gut: organisatorische Unabhängigkeit. Wenn mehrere Teams Features für dieselbe benutzerorientierte Anwendung in unterschiedlichen Zeitplänen liefern müssen und die Koordination zwischen diesen Teams zum Engpass geworden ist, können Micro-Frontends helfen.

Das ist alles. Sie machen Ihre App nicht schneller (meistens das Gegenteil). Sie machen Ihren Code nicht sauberer. Sie reduzieren keine Komplexität. Sie tauschen technische Komplexität gegen organisatorische Flexibilität.

Bei AXA hatten wir genau dieses Problem. Mehrere Teams waren für verschiedene Teile der B2C-Kundenerfahrung verantwortlich: Versicherungsangebote, Schadensfälle, Policenverwaltung und Onboarding. Jedes Team hatte seinen eigenen Sprint-Rhythmus, seine eigenen Prioritäten und seinen eigenen Release-Zeitplan. Ein monolithisches Frontend bedeutete, dass ein Bug im Schadensmodul ein Release des Angebotsmoduls blockieren konnte. Teams verbrachten mehr Zeit mit der Koordination von Deployments als mit dem Entwickeln von Features.

Wie wir es bei AXA umgesetzt haben

Unser Ansatz war pragmatisch, nicht ideologisch. Wir verfolgten nicht den Microservices-Traum von «jedes Team wählt sein eigenes Framework». Wir standardisierten auf React und TypeScript — die Freiheit lag in der Deployment-Unabhängigkeit, nicht in der Technologiewahl.

Jedes Micro-Frontend war ein eigenes JS-Bundle auf dem Server. Vendor-Code wurde in einen gemeinsamen Chunk extrahiert, damit Apps keine Abhängigkeiten duplizierten. Wir bauten eigenes Rollup-Tooling, das es erlaubte, alles auf einmal über AEM CMS zu releasen, aber auch einzelnen Teams ermöglichte, nur ihr JS-Bundle auf dem Server für Hotfixes zu überschreiben — ohne volles Release.

Ein Aggregator verband die Apps, und eine Docking-Klasse übernahm die App-übergreifende Kommunikation — geteilter State, Benutzerkontext und Tokens. Jede App erstellte automatisch ihre eigene Shell und konnte entscheiden, ob sie Authentifizierung benötigte oder anonym lief. Das bedeutete, dass das Angebots-Team unabhängig deployen konnte, während das Schadens-Team in eigenem Tempo arbeitete.

Darüber hinaus gründete ich eine gemeinsame Komponentenbibliothek mit Web Components — framework-agnostisch, sodass jedes Team dieselben UI-Bausteine nutzte. Sie passte direkt in die Micro-Frontend-Architektur: konsistente UX über alle Apps hinweg, gepflegt von einem dedizierten Style-Guide-Team.

// Each micro-frontend = its own JS bundle on the server
// Vendor JS is shared across all apps in a common chunk
// Rollup builds each app independently

// apps/quotes/rollup.config.js → /server/static/quotes.js
// apps/claims/rollup.config.js → /server/static/claims.js
// shared/vendor.js             → /server/static/vendor.js

// Aggregator collects all apps and provides a docking class
// for cross-app communication (shared state, auth tokens)
class AppDocking {
  private shared = new Map<string, unknown>();
  set(key: string, value: unknown) { this.shared.set(key, value); }
  get<T>(key: string): T { return this.shared.get(key) as T; }
}

// Each app auto-creates its own shell and decides
// whether it needs an access token or runs anonymously
interface AppConfig {
  name: string;
  entry: string;             // /server/static/quotes.js
  container: string;         // #quotes-root
  requiresAuth: boolean;     // app decides this
}

// Deploy all apps at once via AEM CMS release,
// OR override individual JS on the server for hotfixes
// App X deploys → only /server/static/x.js gets replaced

Die echten Kosten, über die niemand spricht

Hier ist, was die Konferenzvorträge auslassen:

Gemeinsamer State ist schwierig. Wirklich schwierig. Wenn zwei Micro-Frontends Benutzerkontext, Warenkorb-Status oder Benachrichtigungszähler teilen müssen, bauen Sie im Grunde ein verteiltes System im Browser. Wir haben Wochen damit verbracht, den Authentifizierungs-State zuverlässig über Module hinweg ohne Race Conditions zu synchronisieren.

Konsistente UX ist teuer. Wenn Module unabhängig deployed werden, schleichen sich visuelle Inkonsistenzen ein. Modul A wird mit aktualisierten Button-Styles ausgeliefert, während Modul B noch die alten hat. Wir lösten dies mit unserer Web-Components-basierten Style-Guide-Bibliothek — framework-agnostische Komponenten, die jedes Modul nutzte. Aber der Aufbau und die Pflege dieser Bibliothek war eine erhebliche Investition.

Der Performance-Overhead ist real. Jedes Modul bringt seinen eigenen Laufzeit-Overhead mit. Selbst wenn gemeinsame Abhängigkeiten in einen Common Chunk extrahiert werden, ist der initiale Load schwerer als bei einer einzelnen Anwendung. Bei AXA stieg unsere Time to Interactive um etwa 800ms im Vergleich zum Monolithen. Wir optimierten dies mit aggressivem Lazy Loading und Prefetching, aber der Overhead verschwand nie vollständig.

Die Developer Experience leidet. Die vollständige Anwendung lokal zu betreiben bedeutet, mehrere Dev-Server zu orchestrieren. Debugging über Modulgrenzen hinweg ist mühsam. Integrationstests erfordern, dass alle Module verfügbar sind. Wir bauten erhebliches Tooling, um dies handhabbar zu machen, was selbst einen Wartungsaufwand darstellte.

Wann ich mich dagegen entschieden habe

Bei Migros, beim Aufbau von Bikeworld und Micasa, hatten wir ein ähnliches oberflächliches Problem: mehrere Shops, die sich Infrastruktur teilen. Der Instinkt wäre vielleicht gewesen, Micro-Frontends zu verwenden. Aber ich wählte stattdessen ein PNPM-Monorepo mit Turbo.

Warum? Weil das organisatorische Problem ein anderes war. Wir hatten ein Team, das beide Shops baute, nicht mehrere Teams, die unabhängige Deployments brauchten. Die Shops teilten ein Design System und etwas Infrastruktur, hatten aber unterschiedliche Produktseiten und Checkout-Flows. Ein Monorepo mit geteilten Packages gab uns Code-Wiederverwendung ohne die Deployment-Komplexität.

Bei Vontobel, beim Aufbau einer einzelnen komplexen Finanzplattform, wären Micro-Frontends reiner Overhead gewesen. Ein Team, ein Deployment-Ziel, ein Release-Zyklus. Eine gut strukturierte Next.js-Anwendung mit klaren Modulgrenzen — durchgesetzt durch Linting-Regeln und Code Review, nicht durch Laufzeit-Isolation — war die richtige Antwort.

Bei einer grossen Schweizer Bank habe ich Micro-Frontends in eine andere Richtung geführt: Webpack Module Federation mit einem Turborepo-basierten Monorepo über 3 Teams. Anders als AXAs Custom-Rollup-Ansatz, wo JS-Bundles auf dem Server lagen und einzeln überschrieben werden konnten, aggregiert Module Federation zur Laufzeit — die Shell lädt Remote-Module on demand. Das Monorepo gibt uns geteiltes Tooling und konsistente Code-Standards, während Federation jedem Team volle Deployment-Unabhängigkeit gibt. Zwei sehr unterschiedliche Implementierungen desselben Prinzips: Teams, die ohne aufeinander zu warten shippen können.

Das Entscheidungs-Framework

Nachdem ich mit Micro-Frontends gelebt und Alternativen gewählt habe, hier mein Framework dafür, wann sie Sinn machen:

Verwenden Sie Micro-Frontends wenn: Sie 4+ Teams haben, die an dieselbe Anwendung liefern, Deployment-Koordination zu einem messbaren Engpass geworden ist (nicht nur ein Ärgernis), Teams wirklich unterschiedliche Release-Rhythmen haben, und Sie die Engineering-Kapazität haben, die erforderliche Infrastruktur aufzubauen und zu pflegen (Shell App, gemeinsame Bibliotheken, Tooling, CI/CD-Pipelines).

Verwenden Sie keine Micro-Frontends wenn: Sie weniger als 3 Teams haben, Sie verschiedene Frameworks pro Modul verwenden möchten (die UX-Kosten sind zu hoch), Ihre Anwendung starke modulübergreifende Datenabhängigkeiten hat, Sie keine dedizierte Platform-Engineering-Kapazität haben, oder Sie sie wählen, weil sie trendig sind, und nicht weil Sie den Schmerz gespürt haben, den sie lösen.

// 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 />;
}

Architektur handelt von Kompromissen

Die beste Architektur ist die einfachste, die Ihre tatsächlichen Probleme löst. Nicht die Probleme, die Sie in zwei Jahren haben könnten. Nicht die Probleme, die das Unternehmen hatte, das diesen Medium-Artikel geschrieben hat. Ihre Probleme, heute.

Micro-Frontends lösten ein echtes Problem bei AXA — Custom-Rollup-Bundles auf einem Server, Docking-Klasse für geteilten State, individuelles Deploy über AEM CMS. Bei einer grossen Schweizer Bank bekam dasselbe Problem eine andere Lösung: Webpack Module Federation mit Runtime-Aggregation aus einem Monorepo. Beides funktioniert. Die Implementierung unterscheidet sich, weil die Rahmenbedingungen sich unterscheiden. Ein Monorepo löste ein echtes Problem bei Migros. Eine gut strukturierte Einzelanwendung löste ein echtes Problem bei Vontobel. Das Muster hat sich nicht geändert — verstehen Sie zuerst das Problem, dann wählen Sie die einfachste Lösung, die es adressiert.

Wenn sich das nach YAGNI, angewandt auf Architektur, anhört — genau das ist es. Die Prinzipien sind auf jeder Abstraktionsebene die gleichen.