Skip to content

Microfrontends Unlocked: Webpack 5 Module Federation vs. Legacy Approaches

Why IFrames and build-time NPM packages failed us, and how Webpack 5 Module Federation revolutionized frontend architecture.

Krishna ChandakKrishna Chandak7 min read

There is a common misconception that "Module Federation" and "Webpack 5" are competing approaches to Microfrontends. In reality, Module Federation is the crown jewel of Webpack 5. The true architectural battle is between modern Run-Time Integration (Module Federation) and the legacy approaches we used to rely on: IFrames, Nginx routing, and Build-Time NPM composition. If you have spent years wrestling with frontend monoliths that take 20 minutes to build, this guide will show you how Webpack 5 changes the game.

What you'll learn

  • Understand why traditional microfrontend patterns (NPM, IFrames) hit scaling walls
  • Explain the mechanics of Webpack 5 Module Federation (Host vs. Remote)
  • Configure shared dependencies to prevent loading React multiple times
  • Design a resilient, federated UI architecture with boundary fallbacks

The Dark Ages: Pre-Webpack 5 Microfrontends

To appreciate Module Federation, you have to understand the pain it cures. When backend teams moved to microservices, frontend teams tried to follow suit. But browsers do not have the luxury of internal networks and Kubernetes DNS; everything must eventually execute in a single JavaScript runtime on the user's device.

Historically, we tried three flawed approaches:

  1. The IFrame: Pure isolation, but a nightmare for UX. Modals get clipped, CSS cannot be shared, and passing data requires clunky postMessage APIs.
  2. Edge Routing (Nginx): Different paths (/cart, /catalog) point to different independent apps. It works, but navigating between them causes hard page reloads, destroying the Single Page Application (SPA) experience.
  3. Build-Time Integration (NPM Packages): Team A publishes the Checkout component as an NPM package. Team B (the Host) installs it.
    • The fatal flaw: If Team A fixes a critical bug, Team B has to bump the version, rebuild the entire monolith, and redeploy. You haven't decoupled your deployments; you just decoupled your repos.

Enter Webpack 5 Module Federation

Module Federation allows a JavaScript application to dynamically load code from another application at run-time.

There is no NPM installation. There is no hard page reload. The Host app fetches the compiled JavaScript chunks over the network exactly when they are needed.

ArchitectureRun-time vs Build-time composition
Module Federation decouples deployments. The Host fetches the latest Remote code automatically.

The Mental Model: Hosts and Remotes

In Module Federation, an application can be a Host (the shell that loads other apps), a Remote (the app being loaded), or Bidirectional (both a host and a remote).

A Remote application uses the ModuleFederationPlugin to expose specific files (like a React component or a utility function). Webpack compiles this exposed code into a special entry file (usually called remoteEntry.js). The Host application simply points to this file.

The Code: Wiring it up

Let's look at how two independent React applications configure Webpack 5 to federate a module.

Remote App (Cart) - webpack.config.js
javascript
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
 
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "cartApp", // The global variable name for the remote
      filename: "remoteEntry.js", // The manifest file generated
      exposes: {
        "./MiniCart": "./src/components/MiniCart", // What we are sharing
      },
      shared: { react: { singleton: true }, "react-dom": { singleton: true } },
    }),
  ],
};
Host App (Shell) - webpack.config.js
javascript
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
 
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "hostApp",
      remotes: {
        // "cartApp" maps to the name in the remote. The URL is where it's hosted.
        cart: "cartApp@https://cdn.mycompany.com/cart/remoteEntry.js",
      },
      shared: { react: { singleton: true }, "react-dom": { singleton: true } },
    }),
  ],
};

In the Host app's React code, using the Remote is as simple as a dynamic import. Webpack intercepts this import and fetches the chunk from the CDN.

Host App - App.jsx
javascript
import React, { Suspense } from "react";
 
// Webpack knows 'cart' is a federated module based on the config!
const MiniCart = React.lazy(() => import("cart/MiniCart"));
 
const App = () => (
  <div>
    <h1>Main E-Commerce Site</h1>
    <Suspense fallback={<div>Loading Cart...</div>}>
      <MiniCart />
    </Suspense>
  </div>
);

The Dependency Dilemma: Shared Modules

If both the Host and the Remote use React, what happens when the Host loads the Remote? Do we download React twice? Does the browser crash because there are two instances of the React Virtual DOM?

This is where the shared configuration in the ModuleFederationPlugin shines.

By declaring shared: { react: { singleton: true } }, you tell Webpack: "If the Host has already loaded React, the Remote should just use the Host's copy. Do not download it again."

Strict Versioning

If you enforce a singleton, you must be careful with version mismatches. If the Host uses React 17 and the Remote demands React 18, Webpack will throw a runtime warning (or error, if strictly configured) because a singleton means only one version can win.

FeatureNPM (Build-Time)IFramesModule Federation
Deployment CouplingHigh (Requires Host rebuild)Low (Independent)Low (Independent)
UX & PerformanceExcellentPoor (Heavy DOM, disjointed)Excellent (Native SPA)
Shared State/DependenciesEasy (Bundled together)Nearly impossiblePowerful via Shared scope
ComplexityLowLowHigh (Runtime orchestration)
Module Federation gives you the UX of a monolith with the deployment agility of IFrames.

In Production

Microfrontends solve organizational scaling issues, but they introduce operational complexity. If a Remote goes down, your Host cannot crash.

In production

Always wrap federated modules in React Error Boundaries. If the CDN hosting cartApp is unreachable, React.lazy will throw an exception. An Error Boundary prevents the entire Shell from white-screening, allowing you to gracefully degrade (e.g., showing a "Cart unavailable" disabled button).

Performance

Be aggressive with your shared dependencies, but lazy with your loading. Don't fetch remoteEntry.js files in the <head> of your document. Let Webpack inject the script tags dynamically when the user actually navigates to the feature that requires them.

Production readiness

0/4

Test your understanding

Test your knowledge

0/1 answered
  1. 1.What is the primary advantage of Webpack 5 Module Federation over publishing Microfrontends as NPM packages?

Interview questions

Interview questions

Key takeaways

  • 1Module Federation is a runtime integration pattern, vastly superior to build-time NPM integration.
  • 2Hosts consume modules; Remotes expose modules. An app can be both.
  • 3The 'shared' configuration prevents bloated bundles and duplicate singletons (like React or Vue).
  • 4With great power comes great responsibility: you must design for network failures and version mismatches.
Krishna Chandak

Frontend System Architect

Krishna Chandak

Frontend system architect focused on scalable UI architecture, design systems and shipping fast, accessible interfaces for complex products.