Building Microfrontends with Webpack Module Federation

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Introduction

As applications scale, maintaining a monolithic front-end becomes challenging: long build times, interdependent releases, and difficulty adopting new technologies. Microfrontends solve these issues by breaking a UI into independently developed and deployed modules. Webpack Module Federation allows these modules—called remotes—to expose components that a central host or container app can load at runtime. This detailed guide walks through planning, configuring, developing, and deploying a fully federated microfrontend architecture.

1. Plan Your Microfrontend Architecture

  1. Identify Bounded Contexts
    • Split your UI by business domain: e.g., catalog, checkout, user-profile.
    • Each domain becomes a remote application with its own repo, CI/CD, and release cycle.
  2. Define Shared Libraries
    • Common dependencies (e.g., React, ReactDOM, utility libraries) must be shared to avoid duplicates.
    • Decide which packages are singletons and required versions.
  3. Routing Strategy
    • The container owns top-level routing; remotes register their root component on specific routes.
    • Use React Router (or your framework’s router) in the container to mount remotes.

2. Configure Module Federation

2.1 Remote Configuration

In the remote’s webpack.config.js, expose components and declare shared deps:

jsCopyEdit// projects/shop/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  entry: './src/bootstrap.js',
  mode: 'production',
  output: { publicPath: 'auto' },
  plugins: [
    new ModuleFederationPlugin({
      name: 'shop',
      filename: 'remoteEntry.js',          // Exposed manifest
      exposes: {
        './ProductCard': './src/ProductCard'
      },
      shared: {
        react: { singleton: true, eager: true, requiredVersion: '^17.0.0' },
        'react-dom': { singleton: true, eager: true, requiredVersion: '^17.0.0' }
      }
    })
  ]
};
  • name: Global var under which the remote is registered.
  • filename: The runtime manifest loaded by the host.
  • exposes: Key/value pairs of exposed module paths.
  • shared: Ensures a single instance of React across remotes and container.

2.2 Host Configuration

In the container’s webpack.config.js, declare remotes and shared deps:

jsCopyEdit// projects/container/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: { publicPath: 'auto' },
  plugins: [
    new ModuleFederationPlugin({
      remotes: {
        shop: 'shop@https://cdn.example.com/shop/remoteEntry.js'
      },
      shared: {
        react: { singleton: true, eager: true, requiredVersion: '^17.0.0' },
        'react-dom': { singleton: true, eager: true, requiredVersion: '^17.0.0' }
      }
    })
  ]
};
  • remotes: Maps the remote name (shop) to the URL of its remoteEntry.js.

3. Bootstrap Remote Applications

3.1 Remote Entry Point

Ensure remotes initialize themselves before exposing modules. In src/bootstrap.js:

jsCopyEditimport('./bootstrapApp').catch(console.error);

// src/bootstrapApp.js
import React from 'react';
import ReactDOM from 'react-dom';
import ProductCard from './ProductCard';

ReactDOM.render(
  <ProductCard productId={123} />,
  document.getElementById('root')
);
  • Dynamic import of bootstrapApp prevents code loading before Module Federation sets up.

4. Consume Remotes in the Container

4.1 Dynamic Imports with React.lazy

In the container’s routing or component:

jsxCopyEdit// src/App.jsx
import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const ShopApp = React.lazy(() => import('shop/ProductCard'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/shop" element={<ShopApp />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

export default App;
  • import('shop/ProductCard') resolves to the exposed module in the remote.

5. Local Development and CORS

5.1 DevServer Settings

In each app’s webpack.config.js, configure the dev server for cross-origin requests:

jsCopyEditdevServer: {
  port: 3001,
  headers: { 'Access-Control-Allow-Origin': '*' },
  hot: true
},
output: { publicPath: 'auto' },
  • Run container on 3000, shop remote on 3001.
  • publicPath: 'auto' lets Webpack determine URL at runtime.

6. Deployment and Versioning

  1. Build and Publish Remotes bashCopyEditcd projects/shop npm run build # Deploy dist/ to CDN at /shop/<version>/
  2. Update Remote URL in Container
    Automate URL updates in CI/CD using a script: bashCopyEditVERSION=$(git describe --tags) sed -i "s|shop@.*|shop@https://cdn.example.com/shop/${VERSION}/remoteEntry.js|" projects/container/webpack.config.js
  3. Canary Releases
    • Deploy new remote versions to a staging CDN path.
    • Point container’s remotes config to staging for smoke tests before production.

7. Best Practices

  • Error Boundaries: Wrap remotes to catch load or runtime errors. jsxCopyEdit<ErrorBoundary fallback={<div>Failed to load</div>}> <Suspense fallback={<div>Loading...</div>}> <ShopApp /> </Suspense> </ErrorBoundary>
  • Auth Handling: Keep authentication logic in the container and pass tokens as props or via a shared context.
  • Performance: Preload critical remotes with <link rel="preload"> in the container’s HTML.
  • Monitoring: Use Real User Monitoring (RUM) to track chunk load times and failures.

Conclusion

Webpack Module Federation empowers teams to build scalable, independently deployable microfrontends, improving development velocity and performance. By defining clear boundaries, sharing dependencies as singletons, and automating deployments, you can evolve monolithic UIs into federated architectures. Start with a proof-of-concept remote, refine configurations, and gradually onboard additional domains—transforming your front-end into a resilient, team-friendly ecosystem.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts