
Ich habe mich in meiner Masterarbeit intensiv mit Micro Frontends beschäftigt und möchte in diesem Artikel einen verständlichen Überblick über dieses Architekturkonzept geben. Micro Frontends bezeichnen einen Ansatz, bei dem eine Web-Anwendung in kleinere, unabhängig entwickelbare Frontend-Teile aufgespalten wird – ähnlich wie Microservices auf Backend-Seite. Jeder Teil (Micro Frontend) kann von einem eigenen Team entwickelt, gewartet und deployt werden, ohne den gesamten Monolithen anfassen zu müssen. Das macht Micro Frontends besonders relevant für große Frontend-Projekte, an denen mehrere Teams parallel arbeiten. In meiner täglichen Arbeit als Entwickler habe ich erlebt, wie traditionelle Single-Page Applications mit zunehmender Größe schwerfällig werden: Deployments dauern länger, unterschiedliche Technologien lassen sich schwer integrieren und Teams treten sich gegenseitig auf die Füße. Micro Frontends versprechen hier Abhilfe, indem sie Komplexität aufteilen und so die Entwicklung skalierbarer und wartbarer Frontends ermöglichen.
Aus geschäftlicher Sicht sind Micro Frontends attraktiv, weil sie eine schnellere Auslieferung neuer Features erlauben. Teams können neue Funktionen unabhängig veröffentlichen, was Time-to-Market verbessert. Außerdem können verschiedene Teile der Anwendung unterschiedliche Technologien verwenden, falls das sinnvoll ist – zum Beispiel könnte ein Team React nutzen, ein anderes Angular, ohne sich gegenseitig zu behindern. Natürlich kommt diese Freiheit nicht ohne Preis: Micro Frontends bringen auch zusätzliche Komplexität in Sachen Integration, Performance und Konsistenz mit sich. In diesem Artikel werde ich die Grundideen, Architekturmuster und Implementierungsansätze von Micro Frontends beleuchten. Ich beschreibe aus meiner Ich-Perspektive, welche Vor- und Nachteile ich in der Praxis festgestellt habe und welche Empfehlungen ich aus den Erkenntnissen meiner Masterarbeit ableite.
Architektur
Bevor wir in die technischen Details gehen, ist es wichtig, das Grundgerüst der Architektur von Micro Frontends zu verstehen. Im Zentrum steht dabei immer eine sogenannte Application Shell (Anwendungshülle). Diese Shell ist gewissermaßen der Rahmen oder das Skelett der Anwendung, das die einzelnen Micro Frontend-Komponenten zusammenhält. Jedes Mal, wenn ein Nutzer die Anwendung lädt, wird zunächst die Application Shell geladen. Sie enthält meist nur das nötigste HTML-Grundgerüst und etwas Logik, um die Micro Frontends zu verwalten. Man kann sie sich als übergeordnete Steuerungskomponente vorstellen: Abhängig von der aktuellen URL oder Nutzereingaben entscheidet die Application Shell, welches Micro Frontend gerade geladen und dem Benutzer angezeigt werden soll, und rendert dieses an einer vorgesehenen Stelle im DOM.

(Application Shell – Aufbau und Interaktionen)
Die Application Shell übernimmt zentrale Aufgaben, die für alle Micro Frontends wichtig sind. Dazu gehören beispielsweise das Routing (also die Steuerung der Navigation innerhalb der Anwendung), die Kommunikation zwischen Micro Frontend-Teilen (damit diese Daten austauschen oder auf Ereignisse reagieren können) und das Laden bzw. Teilen von Bibliotheken. Weil die Application Shell so eine Schlüsselrolle spielt, muss sie sehr zuverlässig und schlank implementiert sein – sie bildet schließlich den Single Point of Failure: Wenn die Shell ausfällt, funktioniert die ganze Anwendung nicht mehr. In meiner Erfahrung ist es daher ratsam, in der Application Shell wirklich nur unverzichtbare Logik unterzubringen (z.B. grundlegende Navigation und Layout), während Features in die Micro Frontends selbst wandern.
Ein weiterer architektonischer Aspekt ist der Schnitt der Anwendung – also wie wir entscheiden, was ein einzelnes Micro Frontend umfasst. Hier gibt es zwei grundlegende Ansätze, die man den vertikalen und horizontalen Schnitt nennen kann:
Vertikaler Schnitt: Bei einem vertikalen Schnitt wird die Anwendung feature-orientiert aufgeteilt. Das bedeutet, jedes Micro Frontend übernimmt einen ganzen User Flow oder eine Domäne end-to-end. Zum Beispiel könnte in einer E-Commerce-Webseite ein Team den Bereich „Produktkatalog“ als eigenes Micro Frontend umsetzen, ein anderes Team den Bereich „Checkout“ und wieder ein anderes „Benutzerprofil“. Jedes dieser Micro Frontends beinhaltet dann alle Schichten für diesen Bereich – von der UI über das entsprechende Routing bis ggf. zur Kommunikation mit dem Backend. In der Praxis resultiert das oft darin, dass ein Micro Frontend eine eigene Single-Page Application ist, die in die Application Shell eingebettet wird. Aus Nutzersicht erscheint es als einzelne Seite der Gesamtanwendung. Der vertikale Schnitt hat den Vorteil, dass jedes Team wirklich unabhängig arbeiten kann. Da immer nur ein Micro Frontend für eine Seite aktiv ist, gibt es kaum Überschneidungen: unterschiedliche Micro Frontends beeinflussen sich nicht direkt, und man hat eine klare Isolation. Auch kann jedes Team frei das passende Framework für seine Domäne wählen (wobei man in der Regel trotzdem versucht, ein einheitliches Look & Feel beizubehalten). Allerdings führt dieser Zuschnitt oft zu Redundanz – viele Micro Frontends müssen ähnliche Komponenten oder Logik (etwa Formularfelder, Buttons, Authentifizierung etc.) jeweils selbst mitbringen, weil eine direkte Wiederverwendung kompliziert ist. Diese Doppelarbeit kann den Pflegeaufwand erhöhen. Zudem können die Unterschiede in UX und Umsetzung zwischen den vertical geschnittenen Teilen wachsen, wenn nicht gut koordiniert wird, da jedes Team „sein Ding“ macht. Insgesamt eignet sich der vertikale Schnitt gut, wenn die Anwendung klare Teilbereiche hat, die möglichst autonom funktionieren sollen – etwa bei einem Portal mit unterschiedlichen unabhängigen Modulen oder Services.
Horizontaler Schnitt: Beim horizontalen Schnitt wird die Anwendung technisch oder layout-orientiert aufgeteilt. Hier zerlegt man eine Seite in mehrere Fragmente, die zusammen erst das vollständige Bild ergeben. Beispielsweise könnte ein Micro Frontend ausschließlich die Header-Navigation bereitstellen, ein anderes Micro Frontend liefert einen Seitenleisten-Widget (z.B. Produktempfehlungen), und ein drittes Micro Frontend rendert den Hauptinhalt der Seite (z.B. die Produktliste). Alle diese Teile erscheinen gleichzeitig auf dem Bildschirm und ergeben zusammen die Seite. Jedes Fragment wird in diesem Ansatz von einem eigenen Team gebaut und idealerweise mit einem eigenen passenden Backend-Service gekoppelt (ähnlich der Bounded Context-Idee bei Microservices). Der Vorteil dieses horizontalen Schnitts ist eine feingranulare Wiederverwendbarkeit: weil jedes Fragment relativ klein und spezialisiert ist, kann man es potenziell in verschiedenen Anwendungen oder Seiten einsetzen. Zum Beispiel könnte das „Warenkorb-Symbol mit Zähler“ als eigenständiges Micro Frontend in sowohl der Produktseite als auch der Profilseite wiederverwendet werden, statt dass jede Domäne ihren eigenen Warenkorb-Zähler entwickelt. Dadurch vermeidet man Doppelungen besser als im vertikalen Ansatz. Allerdings muss man für ein konsistentes Nutzererlebnis einen gemeinsamen Styleguide definieren, damit all diese unabhängig entwickelten Fragmente optisch zueinander passen. Zudem steigt die Komplexität erheblich: Weil mehrere Micro Frontends gleichzeitig auf einer Seite laufen, müssen sie miteinander koexistieren, ohne sich in die Quere zu kommen. Die Kommunikation untereinander ist nur indirekt möglich (z.B. über Events, siehe weiter unten), was klare Absprachen erfordert. Auch technisch muss sichergestellt sein, dass sich verschiedene Frameworks oder Skripte nicht gegenseitig beeinflussen – beispielsweise darf ein globales CSS eines Fragments nicht versehentlich Elemente eines anderen Fragments stylen. Der horizontale Schnitt eignet sich besonders für große, multifunktionale Webanwendungen, in denen viele Teams parallel unterschiedliche Features auf derselben Seite entwickeln. Man erkauft sich damit Flexibilität und Wiederverwendung, zahlt aber mit höherem Koordinationsaufwand und anspruchsvollerer Integration.

(Architektonischer Schnitt – Vertikaler vs. horizontaler Ansatz)
In der Praxis schließen sich diese beiden Ansätze nicht strikt aus, aber es ist hilfreich, sie gedanklich zu trennen. Viele erfolgreiche Micro-Frontend-Architekturen beginnen vertikal (also schneiden die Anwendung entlang von Produktbereichen) und nutzen innerhalb der vertikalen Stücke evtl. horizontal aufgeteilte Komponenten. Unabhängig vom Schnittmuster bleibt jedoch die Application Shell als übergreifende Schicht bestehen, die das Routing und das Laden der jeweiligen Micro Frontends steuert. In der nächsten Sektion schauen wir uns an, wie die zusammengesetzten Teile letztlich dem Benutzer ausgeliefert werden – denn dafür gibt es ebenfalls unterschiedliche Strategien.
Komposition von Micro Frontends
Nachdem also festgelegt ist, wie wir unsere Anwendung in Micro Frontends unterteilen, stellt sich die Frage: Wie werden diese Teile zur Laufzeit zusammengesetzt? Die Art der Komposition beschreibt, an welchem Ort bzw. auf welche Weise die einzelnen Micro Frontends zu einer vollständigen Anwendung zusammengefügt werden. Hier haben sich drei Ansätze etabliert:
- Client-Side Composition (Komposition auf der Client-Seite, also im Browser)
- Edge-Side Composition (Komposition an der Edge, z.B. durch ein CDN nahe beim Benutzer)
- Server-Side Composition (Komposition auf dem Server, bevor die Antwort an den Browser geht)
Jeder dieser Ansätze hat Vor- und Nachteile, auf die ich aus meiner Erfahrung eingehen werde.
Client-Side Composition
Bei der Client-seitigen Komposition wird die Zusammenführung der Micro Frontends vollständig im Browser des Benutzers erledigt. Das bedeutet, der Nutzer lädt zunächst die Application Shell (typischerweise als eine minimalistische HTML-Seite mit etwas JavaScript). Diese Shell entscheidet dann – meist anhand der aktuellen Route (URL) oder anderer Regeln – welche Micro Frontend-Module benötigt werden und lädt diese dynamisch nach. Die Nachladeroutine kann unterschiedlich erfolgen: Ein einfaches Beispiel sind iFrames, die per JavaScript in bestimmte Platzhalter eingefügt werden. Moderne Ansätze nutzen häufig Client Side Includes: Das sind Mechanismen, bei denen die Application Shell per AJAX oder Fetch einzelne Fragment-HTML oder -JS abruft und in definierte Container im DOM einsetzt. Alternativ kann man auch Bundler-spezifische Lösungen nutzen (dazu später mehr, z.B. Webpack Module Federation, die ebenfalls im Browser zur Laufzeit Module lädt).

(Client-Side Composition)
Der größte Vorteil der Client-side Composition ist, dass sie sich nahtlos in Single-Page Applications einfügt. Wenn eure Anwendung sowieso primär client-seitig gerendert ist (also viel JavaScript auf dem Client nutzt, wie bei React, Angular oder Vue), dann ist dieser Weg am wenigsten disruptiv. In meinem Projekt bedeutete das: Wir konnten unsere bestehende SPA Schritt für Schritt in Micro Frontends zerlegen, indem wir die Application Shell als neuen Einstieg schrieben und dann die alten Teile als Micro Frontend-Module integrierten. Aus Entwicklersicht ist das recht komfortabel, da die meisten Webframeworks modulare Konzepte haben – man kann z.B. eine bestehende React-App in mehrere kleinere React-Apps aufteilen und über eine Shell orchestrieren. Frontend-Entwickler fühlen sich hier am meisten zu Hause, da die gesamte Logik im Browser verbleibt und sie nicht viel über besondere Infrastruktur nachdenken müssen. Zudem kann die Application Shell bei client-seitiger Komposition sehr direkt mit den Micro Frontends interagieren: Da alles im selben Browserkontext läuft, kann die Shell z.B. Events abfeuern oder auf globale JavaScript-Objekte zugreifen, um den eingebetteten Apps Bescheid zu geben, wenn etwas passiert (z.B. „lade dieses Fragment neu“).
Natürlich gibt es auch Nachteile: Die initiale Ladezeit der Anwendung kann höher sein, da der Browser erst die Shell und dann weitere Dateien laden muss, bevor der Benutzer die vollständige Seite sieht. Ohne weitere Optimierungen kann es also zu einem „langsamen ersten Rendering“ kommen, insbesondere wenn viele Micro Frontend-Bundles nachgeladen werden müssen. Außerdem muss man bei Client-side Rendering auf SEO achten – Inhalte, die erst per JS nachgeladen werden, sind für Suchmaschinen oder Nutzer ohne JS nicht sofort sichtbar. Frameworks wie single-spa oder Module Federation (die beide client-seitige Komposition erleichtern) bieten Lösungen, aber es bleibt etwas, das man im Blick haben muss. Insgesamt eignet sich die Client-side Composition vor allem, wenn eure Anwendung bereits eine SPA ist oder stark auf client-seitiges Rendering setzt, und euer Team hauptsächlich Frontend-Know-how hat.
Edge-Side Composition
Die Edge-side Composition verlagert das Zusammenbauen der Anwendung an die „Kante“ des Netzwerks, also typischerweise auf einen Content Delivery Network (CDN) Server, der geografisch in der Nähe des Nutzers steht. Die Idee dahinter: Anstatt dass euer Browser fünf verschiedene Micro Frontend-Services kontaktieren muss, um eine Seite zusammenzusetzen, übernimmt ein CDN-Knoten diese Arbeit und liefert dem Browser schon eine fertig kombinierte Seite aus. Technisch wird das oft mit Edge Side Includes (ESI) umgesetzt. ESI ist eine Markup-Sprache, die es erlaubt, an bestimmten Stellen eines HTML-Dokuments Platzhalter einzufügen, die dann vom CDN mit Inhalten gefüllt werden. Man kann sich das wie Server Side Includes vorstellen, nur dass es nicht der originäre Webserver macht, sondern ein vorgelagertes CDN-System. In so einem Dokument würde z.B. die Application Shell als statische HTML-Struktur vom Ursprungsserver kommen, aber an Stellen wie <esi:include src="http://produktliste.mydomain.com/fragment" />
lädt der CDN-Server dann den Inhalt von produktliste.mydomain.com
nach und baut alles zusammen, bevor es an den Browser geht.

(Edge-Side Composition)
Der große Vorteil der Edge-side Composition ist Performance durch Caching. CDNs sind darauf optimiert, Inhalte extrem schnell auszuliefern. Sie halten Kopien der Inhalte an vielen Orten weltweit vor, sodass ein Benutzer meist von einem geografisch nahen Server bedient wird. Außerdem können sie durch Caching-Mechanismen dafür sorgen, dass wiederholte Anfragen rasant beantwortet werden, ohne jedes Mal die Ursprungsserver zu belasten. In meiner Untersuchung zeigte sich, dass dieser Ansatz vor allem dann glänzt, wenn die Seiten für viele Benutzer gleich oder ähnlich aussehen. Ein Paradebeispiel ist die IKEA-Webseite (ein oft zitiertes Beispiel für Edge Composition): Ein Produktkatalog oder eine Shop-Seite hat für alle Nutzer weitgehend denselben Inhalt (vielleicht mit minimalen Personalisierungen), sodass das CDN 90% der Inhalte aus dem Cache liefern kann. Dadurch erhält der Nutzer extrem schnell eine fertige Seite. Für Szenarien mit hohem Traffic und wenig personalisierten Inhalten ist Edge Composition daher sehr attraktiv. Ein weiterer Pluspunkt: Euer Team muss sich nicht selbst um die Skalierung der Infrastruktur kümmern – das macht der CDN-Provider. Ihr konzentriert euch auf die Entwicklung der einzelnen Micro Frontends, und das CDN kümmert sich ums Zusammenpuzzeln und Ausliefern.
Nachteile gibt es aber auch hier: Man begibt sich in eine gewisse Abhängigkeit vom CDN-Anbieter. ESI ist zwar ein Standard, wird aber von verschiedenen Anbietern teils unterschiedlich unterstützt. Wechselt man den Anbieter, muss man möglicherweise das Composition-Setup anpassen. Diese Vendor-Lock-in-Thematik sollte man bedenken. Außerdem ist Edge Composition weniger geeignet, wenn stark personalisierte Inhalte ausgeliefert werden müssen (z.B. ein Dashboard, das für jeden Nutzer völlig andere Daten zeigt). In solchen Fällen greift das Caching kaum, und der CDN-Knoten müsste für jede Anfrage doch wieder alle Fragmente frisch holen – dann verliert man den Performancevorteil weitgehend. Schließlich braucht man für diesen Ansatz auch etwas spezielles Know-how: Nicht jeder im üblichen Webteam kennt sich mit CDN-Konfiguration oder ESI-Templates aus, das Know-how von Backend-/Infrastructure-Experten ist hier gefragt. In meinem Team war das initial eine Hürde, weil die meisten eher Frontend-Erfahrung hatten. Edge-side Composition empfehle ich deshalb vor allem für Webseiten mit hohem Leseanteil (wie Kataloge, Nachrichten, Blogs), wo man mit Caching extrem punkten kann, und weniger für sehr dynamische Web-Apps.
Server-Side Composition
Die dritte Variante ist die Server-seitige Komposition. Hierbei übernimmt ein eigener Backend-Service (den man oft als „Aggregator“ oder BFF – Backend for Frontend – bezeichnen könnte) die Aufgabe, die Micro Frontends zusammenzufügen. Im Unterschied zur Edge-Variante liegt dieser Service allerdings in eurer eigenen Infrastruktur, nicht beim CDN. Der Ablauf sieht typischerweise so aus: Alle Nutzeranfragen treffen zunächst auf ein zentrales Gateway oder Kompositions-Server. Dieser erkennt anhand der URL oder Request-Parameter, welche Micro Frontend-Teile für die Antwort benötigt werden. Dann holt er sich diese Teile von den jeweiligen Micro Frontend-Services (oft als HTML-Snippets oder als bereits gerenderte Komponenten) und baut daraus ein vollständiges HTML-Dokument, das an den Browser gesendet wird. Man kann das auch als eine spezielle Form von Server-Side Rendering (SSR) betrachten – nur dass hier mehrere Microservices beteiligt sind. Es gibt Frameworks und Libraries, die einem diese Arbeit abnehmen, etwa Podium (eine Node.js Library für Micro-Frontend-Komposition) oder Tailor (von Zalando entwickelt, früher unter dem Namen Mosaic9 bekannt). Die meisten nutzen Template-Dateien, in denen Platzhalter für die Micro Frontend-Fragmente definiert sind. Der Kompositions-Server füllt diese Platzhalter zur Laufzeit mit den jeweiligen Inhalten.

(Server-Side Composition)
Der Vorteil der Server-side Composition ist Flexibilität. Da es ein „vollwertiger“ Programmcode ist, der auf dem Server läuft, könnt ihr allerlei zusätzliche Logik einbauen: z.B. Authentifizierung, Logging, A/B-Testing, oder auch Daten aus mehreren Quellen vorab zusammenführen, bevor sie an den Client gehen. Ihr seid nicht auf die beschränkte Logik eines CDN-Templates angewiesen, sondern könnt alles programmieren, was nötig ist. Zudem lässt sich dieser Ansatz sehr gut mit klassischem Server-Side Rendering kombinieren: Die Micro Frontends selbst können ihre Inhalte schon server-seitig rendern, sodass am Ende ein fast komplettes HTML an den Browser geht – das sorgt für schnelle erste Paints und ist gut für SEO. In meiner Masterarbeit haben wir zum Beispiel mit Podium experimentiert: dort gibt es Podlets (die einzelnen Micro Frontend Services) und ein Layout-Server (die Shell). Der Layout-Server holt die Inhalte der Podlets und setzt sie zu einer Seite zusammen. Das funktionierte performant und erlaubte uns, übergreifende Funktionen (wie eine zentrale Nutzer-Session) an diesem zentralen Punkt zu handhaben.
Die Kehrseite: Man verschiebt die Last und Komplexität vom Client auf den Server. Der Kompositions-Server wird zu einer kritischen Komponente, die gut skaliert sein muss – wenn viele Benutzer kommen, muss er alle Anfragen bedienen und dabei vielleicht mehrere Microservices ansprechen. Ist er unterdimensioniert oder fällt er aus, hat das direkte Auswirkungen auf alle Nutzer (ähnlich wie bei der Application Shell im Allgemeinen Konzept). In Stoßzeiten kann der Server zum Flaschenhals werden, wenn nicht genug Ressourcen da sind. Daher muss man hier besonderen Fokus auf Skalierung, Load Balancing und Ausfallsicherheit legen. Außerdem benötigt das Team für die Entwicklung dieser Lösung Backend-Know-how. Wenn eure Entwickler hauptsächlich Frontend-Erfahrung haben, müsst ihr eventuell in neue Skills investieren oder eng mit einer Backend-Abteilung zusammenarbeiten. Server-side Composition eignet sich erfahrungsgemäß gut für Webanwendungen, die stark auf Performance und initiale Ladegeschwindigkeit angewiesen sind (weil man hier SSR voll ausspielen kann) und für Szenarien, wo man die Inhalte gut cachen kann, aber trotzdem mehr Kontrolle will als ein reines CDN bietet. Ein Beispiel ist eine Shopping-Plattform wie Zalando, die diesen Weg gewählt hat: Dort sind die Seiten zwar personalisiert, aber ein Großteil kann dennoch gecacht werden, und man wollte die Integration der Teile serverseitig unter eigener Regie haben, um z.B. personalisierte Empfehlungen oder Ähnliches serverseitig hinzuzufügen.
Abschließend kann man sagen: Die Wahl der Kompositionsmethode ist eine grundsätzliche strategische Entscheidung, die man früh treffen sollte, da ein späterer Wechsel mit viel Aufwand verbunden ist. Jede Methode – ob Client, Edge oder Server – bringt unterschiedliche Anforderungen an Infrastruktur und Team mit sich. In meiner Erfahrung ist es wichtig, diese Entscheidung mit allen beteiligten Teams gemeinsam zu treffen, da sie Architektur und Arbeitsweise maßgeblich beeinflusst.
Wichtige Aspekte für Micro Frontends
Egal für welchen architektonischen Schnitt oder welche Kompositionsart man sich entscheidet, es gibt ein paar technische Aspekte, die in jeder Micro-Frontend-Architektur gelöst werden müssen. Drei der wichtigsten Punkte sind Routing, Datenaustausch zwischen den Micro Frontends und geteilte Bibliotheken. Im Folgenden erläutere ich, warum diese Aspekte wichtig sind und wie man damit umgehen kann.
Routing
In einer klassischen monolithischen Single-Page Application ist das Routing (Steuerung, welche „Seite“ oder Komponente bei welcher URL angezeigt wird) meist an einer zentralen Stelle implementiert (z.B. React Router oder Angular Router). In einer Micro-Frontend-App muss man Routing aufteilen: Globales Routing und lokales Routing. Globales Routing übernimmt die Application Shell. Sie sorgt dafür, dass bei einer bestimmten URL das richtige Micro Frontend aktiviert wird. Beispielsweise könnte die Shell definieren: /produkte/*
lädt das Micro Frontend für den Produktkatalog, /checkout/*
lädt das Checkout-Microfrontend, etc. Sobald die Shell das passende Micro Frontend geladen hat, übernimmt dieses ggf. ein eigenes, lokales Routing für Unterseiten. Das ist typisch im vertikalen Schnitt: dort hat ja jedes Micro Frontend seine „eigene Seite“ und kann innerhalb dieser Seite eigene Routen handhaben (z.B. ein Wizard-Schritt im Checkout als Unter-Route). Im horizontalen Schnitt ist globales Routing oft weniger komplex, da meist eine Seite aus mehreren Fragmenten besteht, die Shell also vielleicht nur zwischen groben Seiten (wie „Dashboard“ vs. „Einstellungen“) unterscheidet. Wichtig ist: Die Browser-URL soll idealerweise trotzdem durchgängig funktionieren. Nutzer wollen ein Lesezeichen setzen oder per Browser-Zurück navigieren können, ohne dass die Micro-Frontend-Architektur das bricht. Deshalb muss die Application Shell Änderungen der URL mitbekommen (z.B. via History API) und an die richtigen Micro Frontends weitergeben. In meinem Projekt haben wir dafür eine zentrale Routing-Service-Komponente in der Shell gebaut, die mit den Micro Frontends abgestimmt war. Einige Frameworks wie single-spa bringen ebenfalls eingebaute Lösungen mit, um das Routing zwischen Shell und Micro Frontends zu koordinieren. Mein Fazit: Routing erfordert in Micro Frontends etwas mehr Planung, insbesondere um Deep Links und die Synchronisation zwischen Shell- und Microfrontend-Routen sicherzustellen. Aber mit klarer Aufgabenteilung (Shell macht grob, Microfrontend macht fein) lässt es sich gut umsetzen.
Datenaustausch zwischen Micro Frontends
Ein häufiger Trugschluss am Anfang ist: „Wir teilen alles auf, also müssen die Micro Frontends gar nicht miteinander reden.“ In der Realität gibt es jedoch oft Szenarien, wo Daten von einem Micro Frontend zum anderen fließen sollen. Ein klassisches Beispiel: In einem Shop hat der Nutzer im Produktlisten-Microfrontend einen Artikel in den Warenkorb gelegt – nun soll das separate Warenkorb-Widget-Microfrontend (z.B. im Header) seinen Zähler erhöhen. Da unterschiedliche Micro Frontends in der Regel keine direkte Funktionalität teilen (sie laufen evtl. sogar in unterschiedlichen iframes oder JavaScript-Kontexten), braucht es definierte Wege für den Datenaustausch. Die indirekte Kommunikation erfolgt oft eventbasiert. Man kann z.B. das Browser-Event-System nutzen: Ein Micro Frontend dispatcht ein Custom Event (z.B. window.dispatchEvent(new CustomEvent('itemAddedToCart', { detail: { itemId: 123 } }))
), und ein anderes Micro Frontend lauscht darauf und reagiert. Dieses Muster („Events hochreichen, Properties runterreichen“) kennen wir auch aus komponentenbasierten Frameworks, und es lässt sich in größerem Maßstab anwenden. Andere Möglichkeiten sind Shared State Stores: Die Application Shell könnte einen globalen State (z.B. via Redux oder eine simple JS-Klasse) halten, auf den alle zugreifen. Allerdings koppelt das die Micro Frontends wieder enger an die Shell, was man ja eigentlich minimieren will. In Fällen wie iFrames, wo wirklich eine harte Isolation besteht, kann man über die postMessage
API kommunizieren – dort müssen aber Shell und Microfrontend auf derselben Domain laufen (wegen Same-Origin-Policy) oder man nimmt Umwege über Browser-Storage/BroadcastChannel. In meiner Erfahrung ist es am saubersten, wenn Micro Frontends möglichst losgelöst voneinander funktionieren (im Idealfall brauchen sie gar keinen direkten Datenaustausch). Wenn doch, dann sollte man klare Schnittstellen definieren: Etwa festlegen, welche Events von der Shell oder anderen MFs gesendet und empfangen werden können. So eine „Vertragsdefinition“ verhindert Chaos und erleichtert das Testen. In meinem Team haben wir beispielsweise vereinbart, dass Micro Frontend A bei bestimmten Aktionen ein Event X wirft, und Micro Frontend B hat einen Event Listener dafür. Diese Entkopplung hat gut funktioniert. Zusammengefasst: Datenaustausch ist möglich, erfordert aber bewusste Architekturentscheidungen – oft sind Browser Events oder ein zentrales Pub/Sub-System sinnvolle Ansätze, um Micro Frontends miteinander sprechen zu lassen, ohne sie eng zu koppeln.
Geteilte Bibliotheken
Ein Knackpunkt bei Micro Frontends ist der Umgang mit gemeinsamen Bibliotheken und Abhängigkeiten. In einem Monolithen lädt man zum Beispiel einmal React, einmal lodash, einmal die firmeneigene Design-System-Bibliothek – und alle Teile der App nutzen diese Instanz. In einer Micro-Frontend-Welt besteht die Gefahr, dass jedes Micro Frontend seine eigenen Kopien der gleichen Bibliothek mitbringt. Dann lädt der Benutzer im Worst Case zig Mal React, zig Mal Stylesheets, was natürlich ineffizient ist und auch zu Inkonsistenzen führen kann (unterschiedliche Versionen!). Daher sollte man früh überlegen, wie man Libraries teilen oder zentral bereitstellen kann. Es gibt dafür verschiedene Strategien:
- Zentrales Laden in der Application Shell: Man kann wichtige Libraries in der Shell via „-Tag global laden (z.B. React auf
window.React
setzen). Alle Micro Frontends benutzen dann diese globale Instanz. Das erfordert allerdings, dass alle kompatible Versionen nutzen und auf die globale Bereitstellung vertrauen – weniger sauber gekapselt, aber effektiv, um Doppel-Downloads zu vermeiden. - Module Federation (Webpack 5): Hierbei können Micro Frontends so konfiguriert werden, dass sie bestimmte Abhängigkeiten als „shared“ deklarieren. Webpack stellt dann sicher, dass eine gemeinsame Nutzung stattfindet – z.B. lädt das Host-Shell-Bundle React und markiert es als shared, und die Micro Frontend-Bundles nutzen dann diese eine Instanz. Darauf gehe ich im nächsten Abschnitt noch tiefer ein.
- Libraries mehrfach laden, aber isolieren: In manchen Fällen ist es akzeptabel, dass jedes Micro Frontend seine eigene Bibliothek lädt, z.B. wenn unterschiedliche Versionen nötig sind. Dann muss man aber darauf achten, dass keine globalen Konflikte entstehen. Web Components bspw. können verschiedene Frameworks mitbringen, ohne sich gegenseitig zu stören, solange sie isoliert bleiben.
- CSS und Assets: Neben JS-Bibliotheken denke man auch an CSS-Frameworks (z.B. Bootstrap) oder Web Fonts. Auch hier muss man entscheiden, ob die Shell z.B. ein zentrales CSS lädt, das alle nutzen, oder ob jedes Fragment sein CSS kapselt (bei horizontalem Schnitt ist ein gemeinsamer Styleguide wichtig, wie erwähnt). Tools wie CSS-In-JS oder Shadow DOM (bei Web Components) können helfen, Styles lokal zu halten, aber globale UI-Consistence muss man trotzdem abstimmen.
In meinem Projekt war das Teilen von Bibliotheken eine Herausforderung, vor allem weil wir mehrere Micro Frontends mit React hatten und nicht wollten, dass React zigmal geladen wird. Wir haben daher mit Webpack Module Federation gearbeitet, um React und einige Utility-Libs zu sharen – das klappte gut, bedingt aber, dass alle Micro Frontends mit der gleichen Webpack-Konfiguration gebaut werden. In Summe ist Dependency-Management bei Micro Frontends komplexer als in monolithischen Apps. Man sollte früh definieren, welche Libraries als gemeinsame Basis dienen sollen (z.B. ein bestimmtes UI-Kit, ein bestimmtes JS-Framework) und welche bewusst unterschiedlich sein dürfen. Auch muss man Techniken einsetzen, damit im Browser am Ende nicht unnötig viel doppelt heruntergeladen wird. Gute Planung hier zahlt sich in Performance und Wartbarkeit aus.
Vergleich der Implementierungsansätze
Nachdem wir nun Konzepte und Architektur besprochen haben, stellt sich die Frage: Wie setzt man Micro Frontends technisch um? Es gibt mittlerweile verschiedene Frameworks, Bibliotheken und Techniken, die helfen, Micro Frontends in die Realität zu bringen. In meiner Masterarbeit habe ich die gängigsten Implementierungsansätze untersucht und ausprobiert. Hier stelle ich die wichtigsten vor, jeweils mit ihren Stärken und Schwächen:
Webpack Module Federation – Micro Frontends via Bundler-Modulsharing: Module Federation ist ein Feature von Webpack 5, das es erlaubt, dynamisch Code aus anderen Bundles zu laden. Die Idee ist, dass man seine verschiedenen Micro Frontends als separate Webpack-Bundles baut, und eines davon (die Application Shell) fungiert als Host. Die anderen deklarieren sich als Remote und können vom Host zur Laufzeit nachgeladen werden. Webpack regelt dabei die Pfade und sogar das Teilen von Dependencies: In der Webpack-Konfiguration kann man angeben, welche Bibliotheken „shared“ sein sollen. So haben wir z.B. unsere Micro Frontends konfiguriert, eine gemeinsame React-Version zu teilen – der Host lädt React einmal, und die Remotes greifen darauf zurück, statt eigene einzubetten. Vorteile: Module Federation erfordert relativ wenig Umstellung, wenn man schon eine Webpack-basierte Anwendung hat. Man kann viele bestehende SPA-Teile weiterverwenden, nur die Build-Konfiguration ergänzen. Es ist zudem framework-agnostisch in dem Sinne, dass es egal ist, ob die Micro Frontends mit React, Vue etc. geschrieben sind – Webpack kann alles Mögliche bundeln. In der Praxis nutzt man Module Federation aber häufig, wenn man ohnehin einen homogenen Stack hat (z.B. alles React), um diesen in kleinere Bundles aufzubrechen. Ein großer Pluspunkt ist die Performance beim Laden: Durch geteilte Bibliotheken und asynchrones Nachladen lädt der Nutzer nur, was er braucht. Nachteile: Module Federation bindet einen an Webpack als Build-Tool. Wenn man später mal etwas anderes nutzen will, muss man das Konzept neu implementieren. Außerdem ist es Stand heute schwierig, verschiedene Frameworks in einer Module-Federation-App zu mischen – etwa React und Angular gleichzeitig. Es geht theoretisch über zusätzliche Tricks (z.B. Wrap in Web Components), aber out-of-the-box ist Module Federation eher für einheitliche Stacks gedacht. Auch erfordert es etwas Sorgfalt beim Deployment: Die verschiedenen Bundles (Host und Remotes) müssen kompatible Versionsstände haben, sonst kann es zur Laufzeit knallen. Insgesamt habe ich Module Federation als sehr einfach und effektiv kennengelernt, besonders wenn man schnell eine existierende App modularisieren will, ohne komplett neue Frameworks einzuführen.
single-spa – Framework für mehrere Frontend-Frameworks: single-spa ist ein populäres Open-Source-Projekt, das speziell für Micro Frontends entwickelt wurde. Es bietet einen Runtime-Manager, der mehrere sogenannte Child Applications (die Micro Frontends) auf einer Seite orchestriert. Single-spa übernimmt das Routing zwischen den Apps, das Mounten und Unmounten der Apps beim Navigieren und stellt Lifecycle-Methoden zur Verfügung, damit jedes Micro Frontend weiß, wann es aktiv werden oder aufräumen soll. Man kann mit single-spa verschiedene Frameworks parallel laufen lassen – z.B. ein Angular-basiertes Micro Frontend neben einem React-basierten. Dafür hat single-spa Adaptoren bzw. Helper-Bibliotheken (z.B. single-spa-react, single-spa-angular), die die jeweilige App in das single-spa-Lifecycle einbinden. Vorteile: Single-spa ist sehr mächtig und flexibel. In meinem Test konnte ich tatsächlich eine kleine Angular-App und eine React-App mittels single-spa gemeinsam betreiben. Es gibt sogar eine Integration mit SystemJS, um Module dynamisch nachzuladen, ähnlich wie Module Federation, nur technology-neutraler. Für Unternehmen, die bereits mehrere Technologie-Stacks in der Frontend-Landschaft haben und diese konsolidiert anzeigen wollen, ist single-spa eine der besten Lösungen. Es bringt auch CLI-Tools mit, um Micro Frontend Projekte aufzusetzen, und eine aktive Community. Nachteile: Die Kehrseite der Mächtigkeit ist Komplexität. Single-spa hat eine Lernkurve und man muss sein Projekt an einige Konventionen anpassen. Es handelt sich letztlich um ein zusätzliches Framework, das man erst verstehen und beherrschen muss. Außerdem bemerkte ich, dass single-spa selbst zwar klein ist, aber dadurch, dass man mehrere Frameworks lädt, kann die Gesamtbundlegröße groß werden, wenn man nicht aufpasst (z.B. React + Angular + Vue auf einmal wäre riesig). Man muss also trotz allem schauen, dass man Ressourcen teilt (single-spa lässt z.B. auch zu, dass man Bibliotheken global teilt, ähnlich wie Module Federation). Ein weiterer Punkt: single-spa wird von einem vergleichsweise kleinen Kernteam gepflegt. Das hat in der Vergangenheit zu etwas Verzögerung geführt, wenn neue Framework-Versionen rauskamen – man war darauf angewiesen, dass single-spa angepasst wird. Aber insgesamt habe ich single-spa als sehr robuste Lösung empfunden, wenn man den Use-Case hat, mehrere unterschiedliche Frontend-Stacks vereinen zu müssen.
Podium – Serverseitiges Micro-Frontend-Framework: Podium ist eine Open-Source-Bibliothek von Podium Labs (entwickelt u.a. von finn.no) für Node.js, die bei der Server-side Composition hilft. Ich habe Podium im Rahmen der serverseitigen Variante getestet. Es teilt die Architektur in Layouts (entspricht der Application Shell auf dem Server) und Podlets (die einzelnen Micro Frontend Services). Jeder Podlet läuft als eigenständiger HTTP-Service und liefert seine Inhalte sowie ein Manifest (Metadaten wie benötigte JS/CSS). Das Layout (Server) lädt diese Podlets, erhält ihre Inhalte und fügt sie zusammen, bevor es die Antwort an den Browser sendet. Vorteile: Podium nimmt viel Boilerplate ab – es gibt z.B. Express-Middleware, um einen Podium-Layout-Server schnell aufzusetzen, und Hilfsfunktionen, um Podlets zu registrieren und abzurufen. In meinem Experiment hatte ich mit wenigen Zeilen Code einen Layout-Server, der zwei Podlets (eine Kopfzeile und einen Content-Bereich) zusammengesetzt hat. Ein großer Vorteil ist, dass Podium Caching und Asset-Management gleich mitdenkt: Es kann die statischen Assets der Podlets (JS, CSS) dem Browser mitliefern und sorgt dafür, dass Podlets nicht ständig neu geholt werden, wenn sie sich nicht geändert haben. Performance-mäßig ist Podium sehr gut, vor allem wenn man SSR in den Podlets nutzt – dann bekommt der Nutzer schnell eine gerenderte Seite. Nachteile: Man muss eine Server-Infrastruktur aufbauen und betreiben. Jedes Podlet ist ein Service – für ein Dutzend Micro Frontends hat man also ein Dutzend kleiner Server, die gemanagt sein wollen (Container, Deployments, Monitoring etc.). Das erhöht den DevOps-Aufwand. Außerdem ist Podium naturgemäß auf Node.js ausgerichtet – wenn euer Team kein Node kann oder eure Backend-Services z.B. in Java laufen, müsstet ihr eine Brücke schlagen (Podium selbst verlangt Node für den Zusammensetzer). In Summe ist Podium ein guter Vertreter der serverseitigen Integration: ideal für maximale Performance und Kontrolle, aber auch aufwändiger in Aufbau und Betrieb. Ich würde Podium (oder ähnliche Server Compose Tools) empfehlen, wenn ihr z.B. bereits eine Microservice-Landschaft habt und eure Frontend-Zusammenführung genauso robust und skalierbar wie eure Backends bauen wollt.
Web Components – Standard-basierte Micro Frontends: Web Components sind eigentlich keine spezifische Micro-Frontend-Technologie, sondern ein Browserstandard, um eigene HTML-Elemente zu definieren (mit Shadow DOM, eigenem Template und Verhalten). Dennoch lassen sie sich hervorragend einsetzen, um Micro Frontends zu kapseln. Die Idee: Jedes Micro Frontend liefert einen oder mehrere Web Components, die dann in der Application Shell einfach wie normale HTML-Tags verwendet werden. Zum Beispiel könnte ein gesamtes Micro Frontend darstellen, das sich intern selbst rendert. **Vorteile:** Web Components funktionieren in allen modernen Browsern und sind **framework-unabhängig**. Man kann ein Micro Frontend in Angular bauen und es als
exportieren, und ein anderes in Vue – am Ende können beide als Custom Elements in einer Seite stehen, ohne voneinander zu wissen. Sie sind besser integrierbar als iframes, da sie Teil des gleichen DOMs sind und auch vom Styling her ins Umfeld passen können (via CSS Custom Properties oder indem man globales CSS weiterhin wirken lässt, sofern man kein Shadow DOM isoliert). Außerdem sind Web Components relativ leichtgewichtig – wenn man sie ohne großes Framework entwickelt, ist es nur ein bisschen Vanilla-JS. In Tests habe ich gesehen, dass Web Components tatsächlich erlauben, mehrere Frameworks parallel zu nutzen: Ich hatte z.B. ein React-basiertes Element und ein Angular-basiertes Element, beide eingebunden auf einer Seite, was ohne Konflikte lief. Nachteile: Web Components bringen von sich aus keine Lösung für Komposition oder Laden. D.h. man braucht dennoch eine Form von Application Shell, die die Web Components einbettet und gegebenenfalls die benötigten Skripte lädt. Oft lädt man einfach alle nötigen Web Component Bundles zu Beginn – was bei sehr vielen oder großen Micro Frontends wiederum die Performance drücken kann. Auch ist die Kommunikation mit Web Components ein Thema: Standardmäßig arbeiten sie nach dem Prinzip „Props down, Events up“ – d.h. man kann ihnen Attribute/Properties setzen und sie feuern Events nach außen. Für einfache Anwendungsfälle reicht das, aber komplexe Daten hin- und herzuschieben erfordert dann auch wieder zentrale Services oder Observer-Patterns. Zudem sind Web Components nicht völlig isoliert: Ein Fehler im globalen JS-Kontext kann sie genauso beeinträchtigen, und sie teilen sich den globalen Browser-Speicher. Anders als iframes schützen sie nicht vor z.B. globalen Namespace-Kollisionen. Bibliothek-Sharing ist auch hier manuell zu lösen (man könnte z.B. in jeder Web Component das gleiche Framework drin haben – ineffizient, oder versuchen, extern zu laden). In meiner Einschätzung sind Web Components ein guter Kompromiss: Sie eignen sich hervorragend, wenn man Micro Frontend-ähnliche Widgets bauen will, die in fremde Seiten integrierbar sind (z.B. ein Partner möchte ein Widget auf seiner Seite einbinden – ein Custom Element ist dafür ideal, besser als ein iFrame). Auch innerhalb eines Unternehmens, wenn verschiedene Teams unterschiedliche Frameworks bevorzugen, kann man vereinbaren, dass alle ihre Outputs als Web Component bereitstellen. Die Nachteile (Kommunikation, geteilte Ressourcen) kann man durch Konventionen und zusätzliche Tools abmildern.
iFrames – Altbewährte Isolation: Ein iFrame einzusetzen ist vielleicht der direkteste Weg, um ein Micro Frontend einzubinden. Man benötigt kaum zusätzliche Tools: einfach <iframe src="…">
im HTML, und schon wird ein komplett eigenständiger „Sub-Browser“ in der Seite geladen. Vorteile: iFrames bieten vollständige Isolation. Das Micro Frontend in einem iFrame läuft wie eine eigenständige Website – es hat seinen eigenen Document-Kontext, eigenes CSS, eigenes JavaScript. Nichts davon beeinflusst den Elternkontext oder andere iFrames. Dadurch hat man keinerlei Probleme mit Namenskonflikten oder unterschiedlichen Framework-Versionen. Zudem ist die Integration super einfach – praktisch jeder Webentwickler kennt iFrames. Man kann sogar externe URLs einbinden, was zum schnellen Prototyping taugt (z.B. bestehende alte Anwendung einfach per iFrame in neue Shell setzen). Nachteile: Diese Isolation kommt mit schwerem Ballast. Jeder iFrame lädt alle Ressourcen separat: eigenes HTML, eigene JS-Bundles, eigene CSS, evtl. gleiche Libraries mehrfach. Das macht Seiten mit vielen iFrames sehr schwerfällig. Bei Tests hat sich gezeigt, dass mehrere parallel geöffnete iFrames zu deutlichen Performance-Einbußen führen können, weil der Browser für jeden iFrame praktisch eine eigene kleine Browser-Instanz fährt. Außerdem ist die Kommunikation zwischen iFrames und der Elternseite eingeschränkt. Aus Sicherheitsgründen dürfen sie nur miteinander reden, wenn sie von der gleichen Domain kommen (Same-Origin-Policy). Andernfalls bleibt nur postMessage
, was umständlicher ist als direkte Funktionsaufrufe. Styling ist ein weiteres Problem: Ein iFrame hat per se keine Ahnung vom umgebenden Layout. Responsives Design über mehrere iFrames hinweg umzusetzen ist schwierig – das iFrame kennt nur seine fixe rechteckige Größe. Man kann zwar mit window.postMessage
Signale schicken, um Höhe anzupassen oder ähnliches, aber das ist alles zusätzlicher Aufwand. Aufgrund dieser Nachteile gilt ein reines iFrame-basiertes Setup selten als dauerhafte Lösung für komplexe Apps. Wann sind iFrames dennoch nützlich? In meiner Erfahrung in zwei Situationen: 1) Schrittweise Migration – Wenn man eine Legacy-App in eine neue Micro-Frontend-Architektur überführen will, kann man alte Teile zunächst via iFrame einbinden. So kann man schnell eine erste lauffähige neue Shell bauen und sukzessive iFrames durch echte Integrationen ersetzen. 2) Externe Einbettung – Wenn man Widgets oder Teile seiner Anwendung Dritten zur Verfügung stellen will, die vielleicht keine Entwickler sind. Ein bekanntes Beispiel: YouTube bietet einen iFrame-Embed-Code für Videos an. Warum? Weil es garantiert immer läuft (unabhängig von der fremden Seite) und der Einbinder sich um nichts kümmern muss außer den <iframe>
zu kopieren. Ähnlich könnten Micro Frontend-Teams iFrames nutzen, um z.B. ein Diagramm-Widget an Partner auszuliefern, ohne dass diese komplexe Integration vornehmen müssen. Zusammengefasst sind iFrames so etwas wie der „Oldie“ unter den Micro-Frontend-Patterns – simpel und robust, aber nicht unbedingt effizient.
Zum Schluss sei erwähnt, dass es noch weitere Implementierungsansätze und Tools gibt (z.B. Piral – ein Framework von smapiot für Micro Frontends, Luigi – ein Micro-Frontend-Framework von SAP, oder spezifische Plattformen). Die oben genannten sind jedoch die, denen man aktuell am häufigsten begegnet. Jedes dieser Tools adressiert die Micro-Frontend-Idee aus einem etwas anderen Blickwinkel: Module Federation eher auf Build-Ebene, single-spa als Laufzeit-Orchestrator, Podium auf Server-Ebene, Web Components als Standard und iFrames als Low-Tech-Lösung.
Ergebnisse aus meiner Masterarbeit
In meiner Masterarbeit habe ich die oben beschriebenen Ansätze nicht nur theoretisch untersucht, sondern auch praktisch evaluiert. Ich habe Prototypen mit Module Federation, single-spa, Podium, Web Components und iFrames gebaut, um herauszufinden, welche Stärken und Schwächen sie in realen Anwendungsfällen haben. Die wesentlichen Erkenntnisse aus dieser Evaluierung möchte ich hier – in der Ich-Form – zusammenfassen:
Module Federation erwies sich als am einfachsten zu integrieren. Gerade wenn man bereits eine moderne Frontend-Buildstrecke hat, kann man mit erstaunlich wenig Aufwand eine Micro-Frontend-Struktur bauen. In meinen Tests war Module Federation in den meisten Standardfällen ideal: Wenn alle Teams beispielsweise auf React setzen und eine große Anwendung modularisieren wollen, geht das mit Module Federation reibungslos. Die Stärke liegt in der simplen Nutzung und der guten Performance durch Shared Dependencies. Eine Schwäche zeigte sich allerdings: Sobald man versuchte, verschiedene Frameworks ins Spiel zu bringen (z.B. ein Micro Frontend in Angular, eins in React), stieß Module Federation an Grenzen. Es ist zwar nicht unmöglich, aber ohne zusätzliche Kniffe schwer. Hier musste ich auf andere Lösungen ausweichen.
single-spa war der klare Gewinner, wenn es darum ging, mehrere Frameworks gleichzeitig in einer Anwendung zu unterstützen. In meiner Evaluierung konnte ich mit single-spa Micro Frontends auf Basis von React, Angular und VanillaJS parallel laufen lassen. Die Stärke von single-spa ist diese Flexibilität und das reichhaltige Ökosystem (Adapters, Helpers, CLI). Es ist quasi ein Baukasten für Micro Frontends. Die Schwächen liegen in der Komplexität: Das Setup und die Einarbeitung sind aufwändiger, und man muss sich darauf einlassen, dass single-spa die App orchestriert. Zudem merkte ich, dass man sehr diszipliniert sein muss, Bibliotheken richtig zu teilen, um nicht enorme Bundlegrößen zu bekommen (da single-spa an sich nicht automatisch teilt – man kann aber SystemJS oder Module Federation innerhalb single-spa verwenden, was wiederum komplexer wird). Nichtsdestotrotz: Wenn ein Use-Case Micro Frontends mit heterogenem Tech-Stack erfordert, würde ich aufgrund meiner Ergebnisse single-spa als erste Wahl sehen.
Podium (Server-Side Composition) spielte seinen Trumpf bei der Performance aus. In meinen Lasttests konnte eine Podium-basierte Lösung Seiten schneller ausliefern als die rein client-seitigen Ansätze, besonders wenn wir Server-Side Rendering für die Micro Frontends aktiviert hatten. Der Vorteil, alles serverseitig vorzubereiten, zeigte sich deutlich in der Load-Time und auch darin, dass schwächere Endgeräte weniger zu tun hatten (der Server übernahm ja die Render-Arbeit). Die Stärken von Podium liegen in dieser Geschwindigkeit und der Möglichkeit, übergreifende Funktionen zentral zu managen (z.B. ein Logging jeder Anfrage an zentraler Stelle, konsistente Auth für alle Fragmente etc.). Die Schwächen, die meine Evaluierung offenbarte: Der Implementierungsaufwand und die Infrastruktur sind höher. Für kleine Teams oder Projekte, die nicht schon ein Microservices-Setup haben, wäre Podium vermutlich zu viel des Guten. Außerdem muss man die Komplexität auf dem Server handeln können – Testing, Debugging und Fehlerhandling sind verteilter, weil bei einem Fehler evtl. unklar ist, ob er im Podlet A, Podlet B oder im Layout-Service passierte. Unterm Strich fand ich Podium am besten geeignet für Fälle, wo Performance oberste Priorität hat und man dafür in Kauf nimmt, mehr Backend-Engineering reinzustecken.
Web Components und iFrames habe ich in meiner Arbeit vor allem hinsichtlich Wiederverwendung und Unabhängigkeit untersucht. Die Erkenntnis hier: Beide Ansätze sind optimal, wenn man Micro Frontend-Module bauen will, die in vielen verschiedenen Umgebungen laufen sollen oder wo man die Integrationsumgebung nicht kontrolliert. Zum Beispiel, wenn mein Team ein Widget entwickelt, das von anderen Teams oder sogar externen Partnern in deren Websites eingebunden wird. Web Components und iFrames erlauben so eine Nutzung, ohne dass die Einbinder viel von unserer Technik verstehen müssen – sie integrieren einfach ein Element oder Tag. iFrames sind dabei einfacher zu verwenden (für den Integrator – copy & paste), bieten aber kaum Interaktionsmöglichkeiten mit der hostenden Anwendung. Das habe ich beim Testen deutlich gemerkt: Daten zwischen iFrame und Elternseite auszutauschen war fummelig, und vieles (z.B. gemeinsames Styling) geht gar nicht. Web Components hingegen ließen sich besser in eine bestehende Seite einfügen und interagieren (über Events/Props) begrenzt mit ihrer Umgebung, setzten aber voraus, dass der Integrator evtl. Skripte einbindet und dass die Web-Component-Pakete geladen werden. Insgesamt habe ich gelernt: Für hochgradige Wiederverwendung oder Distribution von Frontend-Modulen sind Web Components und iFrames die Werkzeuge der Wahl. Wenn Einfachheit und Isolierung wichtiger sind als Integration, dann iFrames. Wenn man mehr Integrationsfähigkeit möchte und ein moderneres Ergebnis, dann Web Components – aber dann muss man mehr Vorgaben an den Host machen (z.B. „lade diese JS-Datei, um meine Komponente nutzen zu können“).
Diese Ergebnisse haben mir gezeigt, dass kein Ansatz pauschal „der Beste“ ist – es hängt sehr von den Zielen und Randbedingungen ab. Module Federation glänzt in homogenen Landschaften, single-spa in heterogenen; Podium bei Performance-hungrigen Anwendungen; Web Components/iFrames bei Wiederverwendbarkeit in unbekannten Kontexten. Ein interessantes Nebenergebnis meiner Arbeit war auch ein Entscheidungsmodell mit vier Schritten, das hilft, je nach Projektkontext die richtige Wahl zu treffen – das würde aber den Rahmen dieses Artikels sprengen.
Fazit und Empfehlungen
Zum Abschluss möchte ich – basierend auf meinen Erfahrungen und den Erkenntnissen aus der Masterarbeit – einige Empfehlungen geben, welche Micro-Frontend-Ansätze sich für welche Einsatzzwecke eignen:
1. Wahl des architektonischen Schnitts: Überlege dir zuerst, wie du deine Anwendung aufteilen willst.
- Ist deine Anwendung logisch in klare Domänen oder User Flows getrennt (z.B. Produkt suchen vs. Produkt kaufen)? Dann tendiere zum vertikalen Schnitt. Er gibt den Teams Autonomie über vollständige Features und hält die Gesamtkomplexität niedriger. Vertikale Micro Frontends sind ideal, um umfangreiche, separate Module zu schaffen, die selten direkt miteinander interagieren müssen.
- Hast du eher ein großes, integriertes Produkt, bei dem viele Features auf denselben Seiten kombiniert werden (z.B. ein komplexes Dashboard mit Widgets aus unterschiedlichen Bereichen)? Dann könnte ein horizontaler Schnitt sinnvoll sein, um einzelne Komponenten wiederverwendbar zu machen. Plane hier aber genug Zeit für Abstimmung und ein solides Design-System ein, damit die vielen Teile später zusammenpassen. Horizontal eignet sich für plattformartige Anwendungen, bei denen Kunden sich ihr Erlebnis aus Bausteinen zusammenstellen (Stichwort „Baukastensystem“). Beachte, dass du bei horizontaler Aufteilung eine leistungsfähige Application Shell brauchst, die die Kommunikation der Teile managed.
2. Kompositionsmethode je nach Anforderungen wählen:
- Wenn du primär eine SPA mit viel Client-Rendering hast und SEO nicht kritisch ist, ist Client-side Composition meist am naheliegendsten. Sie ist einfach umzusetzen und erfordert keine große Infrastrukturänderung.
- Wenn deine Anwendung global viele Nutzer hat, die ähnliche Inhalte abrufen (z.B. eine Nachrichtenwebsite oder Katalogseiten), ziehe Edge-side Composition in Betracht. Mit einem CDN und ESI kannst du enorme Performancegewinne erzielen. Achte aber darauf, ob Personalisierung nötig ist – wenn ja, und Caching kaum greift, bringt Edge wenig.
- Wenn Performance und Kontrolle absolut entscheidend sind – z.B. in einer E-Commerce-Umgebung, wo jede Millisekunde Ladezeit Konversion beeinflusst – könnte eine Server-side Composition sinnvoll sein. Tools wie Podium ermöglichen es, Seiten serverseitig optimal zusammenzustellen und trotzdem modular zu bleiben. Voraussetzung ist, dass dein Team sich um die Backend-Aspekte kümmern kann. Bei geringer Backend-Erfahrung im Team würde ich eher zu client- oder edge-side tendieren.
3. Implementierungstechnologie nach Tech-Stack ausrichten:
- Für einheitliche Technologiestacks (Monokultur): Hier würde ich Webpack Module Federation wärmstens empfehlen. Es passt perfekt, wenn z.B. alle Teams React oder alle Teams Vue nutzen. Du bekommst mit minimalem Aufwand eine modulare Architektur. Achte bei Module Federation darauf, ein gemeinsames Versioning der geteilten Bibliotheken zu pflegen, damit es keine Konflikte gibt.
- Für heterogene Stacks: Greifen unterschiedliche Teams zu unterschiedlichen Frameworks, ist single-spa oft die beste Wahl. Es erlaubt „Polyglot Frontends“ in einer Shell. Plane hier aber Zeit für Training und Prototypen ein – single-spa ist mächtig, aber man muss es richtig einsetzen (z.B. die gemeinsamen Dependencies klug handhaben).
- Für höchste Performance via Server: Wenn du dich für serverseitige Komposition entschieden hast, schau dir Podium oder ähnliche Lösungen an. Sie sind ideal, wenn du Node.js einsetzen kannst. Falls dein Backend komplett z.B. Java-basiert ist, könntest du einen ähnlichen Aggregationsservice in Java bauen oder schauen, ob es entsprechende Libraries gibt (viele Konzepte von Podium lassen sich übertragen).
- Für verteilbare Widgets und unbekannte Hosts: Setze auf Web Components oder iFrames. Möchtest du, dass externe Seiten möglichst einfach ein Stück deiner Anwendung einbetten können (z.B. einen Login-Button, ein Video-Player, ein Mini-Shop), dann ist ein iFrame der unkomplizierteste Weg für die Konsumenten. Willst du es moderner und integrierter, dann gib ihnen eine Web Component. Diese erfordert zwar etwas mehr Setup (ein Script einbinden), fügt sich aber schöner ins Gesamtbild ein und kann mit der Host-Seite interagieren. Interne Projekte können ebenfalls von Web Components profitieren, z.B. um ein Micro Frontend unabhängig vom Rest zu testen oder zu entwickeln.
4. Organisatorische Aspekte nicht vergessen: Die Technik ist nur die halbe Miete. Micro Frontends entfalten ihren Nutzen nur, wenn auch die Teams dahinter entkoppelt arbeiten können. Stelle sicher, dass Zuständigkeiten klar verteilt sind (wer verantwortet welches Micro Frontend, wer die Shell). Definiert gemeinsame Schnittstellen und Konventionen (für Events, Routing, Styling), damit es später nicht zu Missverständnissen kommt. Und plant auch, wie ihr gemeinsame Elemente (z.B. Design-Guide, Login-Flow) koordinieren wollt. Je unabhängiger die Teile, desto wichtiger sind regelmäßige Abstimmungen auf höherer Ebene, damit das Endprodukt konsistent bleibt.
Fazit: Micro Frontends sind kein Allheilmittel, aber in vielen Situationen ein geeigneter Ansatz, um große Webprojekte beherrschbar zu machen. In meiner persönlichen Erfahrung lohnt sich der Aufwand besonders dann, wenn Skalierbarkeit in der Entwicklung (viele Teams, viele Features) gefragt ist oder wenn man Alt und Neu parallel fahren muss. Hat man hingegen ein kleines Team und eine überschaubare Anwendung, ist ein Micro-Frontend-Architektur oft overkill. Für die geeigneten Einsatzzwecke aber kann ich sagen: Module Federation ist mein Go-To für schnelles Aufteilen bestehender SPAs, single-spa die Lösung für multi-framework Orchestrierung, Podium/Server-Komposition das Ass im Ärmel für Performance-kritische Enterprise-Anwendungen, und Web Components/iFrames bleiben wertvolle Werkzeuge für spezielle Anforderungen der Wiederverwendung. Am Ende sollte man die Entscheidung immer vom konkreten Projektbedarf abhängig machen – und hoffentlich hilft dieser Artikel dabei, die Vor- und Nachteile der Optionen abzuwägen. Viel Erfolg beim Bauen eurer Micro Frontends!