DEV Community

Cover image for Micro-Frontends: Webpack Module Federation in React 🧩
Prajapati Paresh
Prajapati Paresh

Posted on Originally published at smarttechdevs.in

Micro-Frontends: Webpack Module Federation in React 🧩

The Frontend Monolith Nightmare

As engineering organizations grow, the backend is almost always split into microservices so that different teams can work independently. However, the frontend is frequently left behind as a massive, monolithic React or Next.js repository. When 60 frontend engineers across 5 different squads (Billing, Authentication, Marketing, Analytics, and E-Commerce) all commit to the same repository, disaster is inevitable.

CI/CD pipelines take 45 minutes to compile the gigantic Webpack bundle. NPM dependency conflicts become a daily blockade. If the Marketing team introduces a fatal bug in the homepage CSS, it prevents the E-Commerce team from deploying a critical fix to the checkout flow. The application becomes a monolithic trap, destroying developer velocity.

At Smart Tech Devs, we break massive user interfaces into decoupled Micro-Frontends using Webpack Module Federation. This architecture allows multiple independent teams to build, test, and deploy their own React applications separately, which are then dynamically stitched together in the user's browser at runtime to form a single, cohesive application.

The Module Federation Paradigm

Prior to Module Federation, building micro-frontends required heavy compromises, like wrapping applications in slow <iframe> tags or publishing components to NPM (which still required the host app to rebuild and deploy to get the updates).

Webpack 5 introduced Module Federation, a profound architectural shift. It introduces two concepts:

  • The Host: The main shell application that the user visits (e.g., the main layout, sidebar, and routing).
  • The Remote: Independent applications deployed on separate servers that expose specific components (e.g., a standalone "Checkout" app).

When the Host application loads, it fetches the JavaScript chunks from the Remote applications over the network. If the Checkout team deploys an update, the Host app receives it instantly on the next page refresh—no host rebuild required.

Phase 1: Architecting the Remote App

Let's imagine the E-Commerce team is building a standalone React application for the "Checkout Cart". In their webpack.config.js, they use the ModuleFederationPlugin to expose their Cart component to the outside world.


// Remote App (Checkout Team) - webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;

module.exports = {
  // ... standard webpack config
  plugins: [
    new ModuleFederationPlugin({
      name: "checkoutApp", // The unique name of this micro-frontend
      filename: "remoteEntry.js", // The manifest file the host will request
      exposes: {
        // Expose the specific React component to the public
        "./Cart": "./src/components/Cart.jsx", 
      },
      shared: {
        // We tell Webpack: "Don't download React again if the host already has it!"
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
      },
    }),
  ],
};

Phase 2: Architecting the Host App

Now, the Core Platform team is building the Host application. They need to configure their Webpack to consume the remote "Checkout Cart" component dynamically over the network.


// Host App (Core Team) - webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "hostApp",
      remotes: {
        // Map the remote app name to its live production URL
        checkoutApp: "checkoutApp@https://checkout.smarttechdevs.in/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, eager: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, eager: true, requiredVersion: deps["react-dom"] },
      },
    }),
  ],
};

Phase 3: Runtime Integration in React

Inside the Host application's React code, consuming the remote component feels exactly like importing a local file, thanks to Webpack's seamless resolution. We use React's lazy and Suspense to handle the network request gracefully.


// Host App - src/App.jsx
import React, { Suspense } from 'react';

// 1. Dynamically import the Cart from the remote server
// The syntax is "remoteName/ExposedModule"
const RemoteCart = React.lazy(() => import('checkoutApp/Cart'));

export default function App() {
  return (
    <div className="min-h-screen bg-gray-50">
      <header className="p-6 bg-blue-900 text-white">
        <h1>Enterprise Platform Shell (Host)</h1>
      </header>

      <main className="p-12">
        <h2>Your Shopping Session</h2>
        
        {/* 2. Wrap the remote component in a Suspense boundary */}
        {/* While the JS chunk is downloading over the network, show a skeleton */}
        <Suspense fallback={<div className="animate-pulse h-64 bg-gray-200 rounded"></div>}>
          <RemoteCart />
        </Suspense>
      </main>
    </div>
  );
}

The Engineering ROI and Next.js Integrations

Architecting Micro-Frontends via Module Federation is the ultimate solution for scaling human teams. It entirely decouples your deployment pipelines. The Checkout team can push a hotfix to production at 2:00 PM on a Friday without ever asking the Core Platform team to approve a PR or trigger a Host rebuild.

Furthermore, because of the shared dependencies configuration, the browser behaves optimally. If both the Host and the Remote rely on lodash, Webpack intelligently negotiates the dependencies at runtime and ensures lodash is only downloaded into the browser once. (Note: For Next.js App Router applications, this exact architecture is achievable using the specialized @module-federation/nextjs-mf package, ensuring full SSR compatibility alongside runtime federation). By adopting this pattern, you bring backend-level microservice autonomy to the browser ecosystem.

Top comments (0)