JS
() =>
JavaScript · Module Federation · Micro-Frontends · Webpack 5
Module Federation und Micro-Frontends
Host, Remote, Shared Dependencies und Deployment

Module Federation in Webpack 5 löst das bisher schwierigste Problem bei Micro-Frontend-Architekturen: Wie mehrere unabhängige Teams ihre JavaScript-Bundles zur Laufzeit kombinieren, ohne gemeinsame Dependencies mehrfach zu laden oder eine zentrale Build-Pipeline zu teilen.

18 Min. Lesezeit Webpack 5 · Vite Federation · Host · Remote · Shared · Dynamic Import Node.js · React · Vue · Vanilla JS

1. Das Problem, das Module Federation löst

Große Frontend-Anwendungen wachsen mit der Zeit zu schwer wartbaren Monolithen heran: Ein einziges Repository, eine einzige Build-Pipeline, ein einziges Deployment. Wenn 15 Teams an derselben Codebase arbeiten, wird jeder Merge zu einem Koordinationsproblem, jedes Deployment zu einem Risiko für alle. Micro-Frontends sind die Antwort: Dieselbe Frontend-Anwendung wird in unabhängige, team-eigene Teile aufgeteilt, die separat entwickelt, getestet und deployed werden können. Das klingt einfach – aber der technische Knackpunkt war lange, wie diese Teile im Browser zusammenkommen, ohne dass React dreimal geladen wird oder globale CSS-Klassen sich überschreiben.

Module Federation, eingeführt mit Webpack 5, ist die erste Build-Tool-native Lösung für dieses Problem. Es erlaubt einer JavaScript-Anwendung, zur Laufzeit Code aus einer anderen, völlig separaten Webpack-Build zu laden – mit expliziter Kontrolle über geteilte Dependencies und ohne den Overhead eines zentralen Build-Schritts. Das ist keine neue Idee: iframes, Script-Tags und Single-SPA haben dasselbe Ziel verfolgt. Aber Module Federation ist die erste Lösung, die tief in das JavaScript-Modulsystem integriert ist und damit natürlich mit modernen Frameworks funktioniert.

2. Host, Remote und Shared: die drei Kernkonzepte

Das Modell von Module Federation baut auf drei Rollen auf. Ein Host ist die Anwendung, die andere Module lädt – typischerweise die Shell oder das App-Skeleton. Ein Remote ist eine Anwendung, die Module für andere Hosts bereitstellt – ein Team-eigener Bereich der Anwendung, der separat deployed wird. Dasselbe Webpack-Bundle kann gleichzeitig Host und Remote sein: Eine Produktdetail-App kann Module von einer Bewertungs-App laden (Remote konsumieren) und gleichzeitig ihre eigenen Komponenten für eine übergeordnete Shell bereitstellen (als Remote auftreten).

Shared ist das dritte Konzept: Libraries, die zwischen Host und Remote geteilt werden sollen, ohne mehrfach in den Browser geladen zu werden. React ist das klassische Beispiel – wenn Host und Remote beide React laden, würden beide React-Instanzen aktiv sein, was zu Bugs führt, weil React intern globalen Zustand (Hooks, Context) nutzt. Mit shared: { react: { singleton: true } } stellt Module Federation sicher, dass nur eine React-Instanz im Browser existiert, egal welcher Teil der Anwendung zuerst geladen wird.


// webpack.config.js — HOST application (App Shell)
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  mode: "development",
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",           // unique name for this application
      remotes: {
        // Reference remote applications by name + their manifest URL
        productApp: "productApp@https://products.mironsoft.de/remoteEntry.js",
        cartApp:    "cartApp@https://cart.mironsoft.de/remoteEntry.js",
      },
      shared: {
        react:     { singleton: true, requiredVersion: "^18.2.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.2.0" },
      },
    }),
  ],
};

// webpack.config.js — REMOTE application (Product Team)
module.exports = {
  mode: "development",
  plugins: [
    new ModuleFederationPlugin({
      name: "productApp",       // must match the key in host remotes
      filename: "remoteEntry.js", // manifest file — must be publicly accessible
      exposes: {
        // Map public names to local module paths
        "./ProductCard":   "./src/components/ProductCard",
        "./ProductDetail": "./src/pages/ProductDetail",
        "./useCart":       "./src/hooks/useCart",
      },
      shared: {
        react:     { singleton: true, requiredVersion: "^18.2.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.2.0" },
      },
    }),
  ],
};

4. Shared Dependencies: Versionierung und Singleton

Das Shared-Dependencies-Konzept in Module Federation ist der komplexeste Teil der Konfiguration und die häufigste Fehlerquelle. Wenn Host und Remote unterschiedliche Versionen einer Bibliothek angeben, entscheidet Module Federation anhand der Semver-Kompatibilität, ob eine Version für beide ausreicht oder ob beide Versionen geladen werden müssen. Mit requiredVersion: "^18.2.0" signalisiert ein Bundle, dass es mit jeder Patch- oder Minor-Version ≥ 18.2.0 kompatibel ist. Ist die höchste kompatible Version bereits geladen, wird sie wiederverwendet – kein zweiter Download.

Das singleton: true-Flag erzwingt, dass maximal eine Instanz im Browser existiert – auch wenn Host und Remote inkompatible Versionen hätten. In diesem Fall wird eine Warnung ausgegeben, und die höhere Version gewinnt. Für React ist Singleton zwingend, weil zwei React-Instanzen im Browser zu dem bekannten "Invalid hook call"-Fehler führen. Für andere Libraries wie Lodash oder Axios ist Singleton optional – mehrere Instanzen koexistieren, die Anwendung funktioniert, verschwendet aber Bandbreite. Die eager: true-Option lädt die Bibliothek sofort statt erst beim ersten Import – wichtig für den App-Einstiegspunkt, der sonst auf den asynchronen Ladevorgang warten muss.

5. Dynamisches Laden von Remote-Modulen zur Laufzeit

Die statische Konfiguration im webpack.config.js ist nur die halbe Geschichte. Module Federation unterstützt auch das vollständig dynamische Laden von Remote-Modulen zur Laufzeit – ohne dass die URL des Remote-Bundles zur Build-Zeit bekannt sein muss. Das ermöglicht Szenarien wie A/B-Tests, bei denen der Server zur Laufzeit entscheidet, welche Version einer Komponente geladen wird, oder Plugin-Systeme, bei denen Endnutzer eigene Module integrieren können.

Das Dynamic Remote Loading Pattern nutzt die __webpack_init_sharing__ und __webpack_share_scopes__ APIs, um den Shared-Scope manuell zu initialisieren, bevor ein dynamisch geladenes Remote-Modul verwendet wird. Im Kern läuft es auf einen import() des Remote-Manifests hinaus, gefolgt von einem Aufruf der init und get-Funktionen des Remote-Containers. Dieser Mechanismus macht Module Federation zur Grundlage für echte Plugin-Architekturen im Frontend, bei denen Drittparteien Module bereitstellen können, die die Host-Anwendung zur Laufzeit integriert.


// Dynamic remote loading — URL not known at build time
async function loadRemoteModule(remoteUrl, scope, module) {
  // Step 1: inject the remote entry script dynamically
  await new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = remoteUrl;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });

  // Step 2: initialize the shared scope
  await __webpack_init_sharing__("default");

  // Step 3: get the container from the window (set by remoteEntry.js)
  const container = window[scope];
  await container.init(__webpack_share_scopes__.default);

  // Step 4: get the requested module factory
  const factory = await container.get(module);
  return factory(); // returns the module
}

// Usage: load any remote at runtime
async function renderProductCard(productId) {
  const { default: ProductCard } = await loadRemoteModule(
    "https://products.mironsoft.de/remoteEntry.js",
    "productApp",   // window[scope] must match the remote's `name`
    "./ProductCard" // must be listed in the remote's `exposes`
  );
  // Use ProductCard as a normal React component
}

// React lazy + Suspense integration
const RemoteProductCard = React.lazy(() =>
  import("productApp/ProductCard") // static reference via webpack remotes config
);
// Usage: <Suspense fallback={<Spinner />}><RemoteProductCard id={1} /></Suspense>

6. Module Federation mit Vite

Während Module Federation nativ in Webpack 5 integriert ist, bietet das Ökosystem mit @originjs/vite-plugin-federation und dem offizielleren @module-federation/vite (ab 2024) Plugins für Vite-basierte Projekte. Die Konfiguration folgt demselben Host/Remote/Shared-Muster wie in Webpack, wird aber als Vite-Plugin in der vite.config.ts eingebunden. Ein wichtiger Unterschied: Vite nutzt im Development-Modus ES Modules und native Browser-Imports, während der Production-Build in ein Webpack-kompatibles Format kompiliert wird.

Die Interoperabilität zwischen Webpack-Hosts und Vite-Remotes (und umgekehrt) ist technisch möglich, erfordert aber sorgfältige Abstimmung der remoteEntry.js-Formate. Die neuere Spezifikation "Module Federation 2.0" zielt auf Build-Tool-Agnostizität ab und soll nahtlose Interoperabilität zwischen Webpack, Vite, Rspack und anderen ermöglichen. Für neue Projekte empfiehlt sich, das gesamte Micro-Frontend-Ökosystem auf dasselbe Build-Tool zu standardisieren und Vite 5 mit @module-federation/vite oder Rspack mit nativem Federation-Support zu verwenden.

7. Routing in Micro-Frontend-Architekturen

Routing ist einer der kniffligsten Aspekte von Micro-Frontend-Architekturen mit Module Federation. Wenn jedes Remote-Modul sein eigenes Router-Setup mitbringt, entstehen Konflikte: Welcher Router ist für welche URL zuständig? Das gängigste Muster ist "Top-Level-Routing im Host, Sub-Routing im Remote": Der Host-Router entscheidet auf Basis des URL-Pfades, welches Remote-Modul geladen wird. Das Remote-Modul erhält eine Base-URL als Prop und rendert sein eigenes Routing unterhalb dieses Basispfades.

Für React-Projekte bedeutet das konkret: Der Host verwendet React Router v6 mit einem <Route path="/products/*">-Catch-all, der das Remote-Modul lädt. Das Remote erhält basename="/products" und rendert seine eigenen Routen relativ dazu. Ein häufiger Bug: Das Remote-Modul importiert BrowserRouter statt MemoryRouter oder Router mit dem vom Host übergebenen History-Objekt. Das führt zu zwei konkurrierenden Router-Instanzen, die beide auf den Browser-History-Stack schreiben und gegenseitig die URL überschreiben.

8. Deployment-Strategien und CDN-Konfiguration

Das größte Versprechen von Module Federation ist das unabhängige Deployment: Jedes Team kann sein Remote-Modul deployen, ohne andere Teams zu informieren. Aber dieses Versprechen funktioniert nur, wenn das Deployment korrekt konfiguriert ist. Der publicPath in der Webpack-Konfiguration muss der tatsächlichen URL entsprechen, unter der das Bundle ausgeliefert wird. Für CDN-Deployments sollte publicPath: "auto" verwendet werden, damit Webpack die URL der remoteEntry.js automatisch als Basis für alle anderen Chunk-URLs verwendet.

Versionierung der Remote-Entries ist entscheidend für Zero-Downtime-Deployments. Wenn ein Host eine neue Version des Remote lädt und das alte Bundle noch im Browser-Cache ist, können Inkompatibilitäten entstehen. Das Standardmuster: remoteEntry.js wird nie gecacht (Cache-Control: no-store oder kurze TTL), während alle anderen Chunks mit inhaltsbasiertem Hashing langfristig gecacht werden. Der Host lädt immer die aktuelle remoteEntry.js, die dann auf die richtigen Chunk-URLs verweist. Dieser Mechanismus macht Module Federation Deployments so zuverlässig wie traditionelle CDN-Deployments.

9. Module Federation vs. andere Ansätze

Es gibt mehrere technische Ansätze für Micro-Frontend-Architekturen, die sich in Isolation, Performance und Komplexität unterscheiden.

Ansatz Isolation Shared Dependencies Komplexität
Module Federation JS-Scope (kein CSS) Automatisch via Plugin Mittel
iframes Vollständige Isolation Keine – alles mehrfach geladen Niedrig
Web Components Shadow DOM für CSS Manuell via Import Maps Mittel
Single-SPA JS-Scope Manuell via SystemJS Hoch
Import Maps Keine Nativ via Browser Niedrig

// vite.config.ts — Vite Module Federation (host)
import { defineConfig } from "vite";
import federation from "@originjs/vite-plugin-federation";

export default defineConfig({
  plugins: [
    federation({
      name: "shell",
      remotes: {
        productApp: "https://products.mironsoft.de/assets/remoteEntry.js",
      },
      shared: ["react", "react-dom"],
    }),
  ],
  build: {
    target: "esnext", // required for top-level await in federation runtime
    minify: false,    // easier debugging during federation setup
  },
});

// vite.config.ts — Vite Module Federation (remote)
export default defineConfig({
  plugins: [
    federation({
      name: "productApp",
      filename: "remoteEntry.js",
      exposes: {
        "./ProductCard": "./src/components/ProductCard.tsx",
      },
      shared: ["react", "react-dom"],
    }),
  ],
  build: {
    target: "esnext",
  },
});

// Usage in host component — same as Webpack federation
// const ProductCard = React.lazy(() => import("productApp/ProductCard"));

Mironsoft

Micro-Frontend-Architektur, Module Federation und Frontend-Infrastruktur

Micro-Frontend-Architektur für euer Team einführen?

Wir entwerfen und implementieren Module Federation Architekturen für Multi-Team-Frontends – von der Webpack/Vite-Konfiguration über Shared-Dependency-Strategien bis zur CDN-Deployment-Pipeline.

Architektur-Design

Host/Remote-Aufteilung, Shared-Dependency-Strategie und Routing-Konzept

Build-Konfiguration

Webpack 5 und Vite Federation Plugin Setup mit CI/CD-Integration

CDN-Deployment

publicPath, Cache-Strategie und Zero-Downtime-Deployment für Remote-Entries

10. Zusammenfassung

Module Federation ist das Fundament moderner Micro-Frontend-Architekturen: Es ermöglicht das Laden von JavaScript-Modulen aus anderen, separat deployten Anwendungen zur Laufzeit. Das Host/Remote/Shared-Modell gibt Teams volle Autonomie bei Entwicklung und Deployment, während der Shared-Dependency-Mechanismus sicherstellt, dass React, Vue und andere Frameworks nicht mehrfach in den Browser geladen werden. Webpack 5 hat Federation nativ integriert; Vite-Plugins schließen die Lücke für Vite-basierte Projekte.

Die wichtigsten Fallstricke: Singleton-Konfiguration für React und Context-abhängige Bibliotheken, korrektes publicPath-Handling für CDN-Deployments und eine klare Routing-Strategie, die verhindert, dass mehrere Router-Instanzen gleichzeitig auf den Browser-History-Stack schreiben. Module Federation ist keine Technologie für jedes Projekt – ab etwa drei unabhängigen Teams mit getrennten Deployment-Zyklen beginnt die Investition in die Infrastruktur jedoch, sich durch reduzierte Koordinationskosten auszuzahlen.

Module Federation und Micro-Frontends — Das Wichtigste auf einen Blick

Host & Remote

Host konsumiert Remote-Module zur Laufzeit. Remote exposed Module via remoteEntry.js. Beide können gleichzeitig Host und Remote sein.

Shared Dependencies

singleton: true für React und Context-abhängige Libs. requiredVersion für Semver-Kompatibilitätsprüfung. eager: true für den App-Einstiegspunkt.

Deployment

publicPath: "auto" für CDN. remoteEntry.js ohne Cache (no-store). Chunks mit Content-Hash langfristig cachen. Teams deployen unabhängig.

Vite & Alternativen

@module-federation/vite für Vite-Projekte. Module Federation 2.0 für Build-Tool-Agnostizität. Rspack mit nativem Federation-Support als Webpack-Alternative.

11. FAQ: Module Federation und Micro-Frontends

1Was ist Module Federation?
Webpack-5-Feature für das Laden von Modulen aus anderen Builds zur Laufzeit. Technische Grundlage für Micro-Frontend-Architekturen mit geteilten Dependencies.
2Host vs. Remote?
Host lädt Module von Remotes. Remote stellt Module über remoteEntry.js bereit. Dieselbe App kann gleichzeitig Host und Remote sein.
3Warum singleton: true für React?
Zwei React-Instanzen → "Invalid hook call". singleton: true erzwingt eine einzige Instanz im Browser.
4Deployment mit Module Federation?
remoteEntry.js: kein Cache. Chunks: Content-Hash + langer Cache. publicPath: auto. Teams deployen unabhängig ohne andere zu blockieren.
5Vite und Module Federation?
@module-federation/vite Plugin. Konfiguration wie Webpack. Build-Target: esnext. Interop Webpack ↔ Vite möglich aber komplex.
6Routing-Konflikte lösen?
Top-Level-Routing im Host, Sub-Routing im Remote mit basename. Remote niemals BrowserRouter importieren – sonst zwei konkurrierende Router.
7Dynamisches Remote-Loading?
Script dynamisch injizieren, __webpack_init_sharing__ aufrufen, window[scope].init + container.get(). Ermöglicht Plugin-Systeme und A/B-Tests.
8Ab wie vielen Teams lohnt es sich?
Ab ca. 3 Teams mit getrennten Deployment-Zyklen. Darunter überwiegt die Infrastruktur-Komplexität den Nutzen.
9Module Federation 2.0?
Build-Tool-agnostische Spezifikation für Webpack, Vite, Rspack-Interop. Standardisiertes remoteEntry-Format als Ziel.
10Unterschied zu iframes?
iframes: vollständige Isolation, aber schwierige UX-Integration. Module Federation: JS-Scope geteilt, natürliche Framework-Integration, keine CSS-Isolation ohne Shadow DOM.