Configuring Webpack Module Federation #

This guide details the architectural patterns and configuration workflows for Configuring Webpack Module Federation in enterprise micro-frontend ecosystems. We cover plugin initialization, runtime dependency resolution, and production deployment strategies to ensure scalable, isolated container communication.

Key Implementation Objectives:

Setup/Config #

Establish foundational ModuleFederationPlugin syntax, define container boundaries, and align output paths. Follow the Step-by-step Webpack 5 container configuration to ensure correct chunk generation and remote entry exposure.

At build time, the plugin generates a remoteEntry.js manifest that exposes module metadata and shared dependency contracts. The output.publicPath must be explicitly aligned with your deployment environment. Hardcoding / will break cross-origin asset resolution; use auto or a dynamic runtime variable to ensure the browser fetches chunks from the correct origin.

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

module.exports = {
 output: {
 publicPath: 'auto', // Critical for CDN/cross-origin deployments
 uniqueName: 'hostApp',
 },
 plugins: [
 new ModuleFederationPlugin({
 name: 'hostApp',
 filename: 'remoteEntry.js',
 exposes: {
 './Header': './src/components/Header.tsx',
 './Auth': './src/providers/AuthProvider.tsx'
 },
 shared: {
 react: { singleton: true, requiredVersion: '^18.2.0' },
 'react-dom': { singleton: true, requiredVersion: '^18.2.0' }
 }
 })
 ]
};

Runtime vs Build-Time Implications: The exposes map is resolved at build time into separate Webpack chunks. At runtime, the host’s remoteEntry.js acts as a dynamic module registry, allowing the browser to fetch exposed components on-demand without full page reloads.

Integration #

Configure runtime dependency sharing, implement dynamic remote consumption, and establish cross-container API contracts. Contrast this approach with Setting Up Vite with Federation Plugins to understand bundler-specific integration trade-offs, particularly around ES module preloading and dev server proxying.

Remote consumption relies on Webpack’s internal sharing scope. Before invoking a remote module, you must initialize the shared scope and bootstrap the remote container. This sequence guarantees that singleton dependencies are resolved before the remote’s execution context is established.

// remoteLoader.ts
const loadRemote = async (scope: string, module: string) => {
 // 1. Initialize host's shared scope
 await __webpack_init_sharing__('default');
 
 // 2. Load remote container from window/global scope
 const container = window[scope as keyof Window];
 if (!container) throw new Error(`Remote container '${scope}' not found`);
 
 // 3. Initialize remote with host's shared dependencies
 await container.init(__webpack_share_scopes__.default);
 
 // 4. Fetch and instantiate the exposed module
 const factory = await container.get(module);
 return factory();
};

// React integration with fallback
import React from 'react';
const RemoteComponent = React.lazy(() =>
 loadRemote('remoteApp', './Dashboard').catch(() => import('./FallbackDashboard'))
);

Runtime vs Build-Time Implications: loadRemote executes entirely at runtime. The __webpack_init_sharing__ and container.init() calls negotiate dependency versions dynamically. Build-time type contracts should be enforced via shared TypeScript interfaces or .d.ts stubs to prevent interface drift between independently deployed containers.

Edge Cases #

Address dependency version collisions, CSS scoping isolation, and state leakage between federated boundaries. Reference Managing Shared Dependencies at Runtime for advanced fallback strategies when strict version matching fails.

Version mismatches are the primary cause of runtime crashes in federated architectures. Webpack’s shared configuration allows granular control over how dependencies are resolved across container boundaries. Use strictVersion to enforce exact matches during development, and configure requiredVersion with semver ranges to allow safe fallbacks in production.

// Advanced shared dependency configuration
shared: {
 'lodash': {
 singleton: true,
 requiredVersion: '^4.17.21',
 eager: false // Lazy-loaded to reduce initial bundle size
 },
 '@mui/material': {
 singleton: true,
 requiredVersion: '^5.14.0',
 strictVersion: true // Throws build/runtime error if incompatible
 }
}

Runtime vs Build-Time Implications: Setting eager: false defers dependency resolution to runtime, keeping the initial chunk lean. When strictVersion: true is enabled, Webpack validates the host’s installed version against the remote’s requiredVersion at build time and throws a hard error if they diverge, preventing silent hook violations or API mismatches.

Testing/Validation #

Validate remote entry loading, mock federation boundaries in CI/CD, and verify shared dependency resolution under network constraints. Implement failure simulation protocols aligned with Debugging remote container loading failures in production.

Federation testing requires mocking the global Webpack sharing APIs. In unit and integration tests, stub __webpack_init_sharing__, __webpack_share_scopes__, and window[scope] to simulate successful container initialization. Use network interception tools to force remoteEntry.js to return 500 or 404 responses, validating that your React.lazy fallbacks and error boundaries trigger correctly.

Validation Checklist:

Deployment #

Configure long-term caching, optimize chunk splitting, and implement CDN-ready deployment patterns for federated assets. Apply Optimizing Webpack cache for faster federation builds to reduce CI pipeline latency and ensure deterministic builds.

Independent container deployment requires careful cache invalidation. remoteEntry.js should be served with Cache-Control: no-cache or must-revalidate to ensure the host always fetches the latest manifest. All other chunks should utilize content hashing ([contenthash]) to enable aggressive long-term caching.

Deployment Strategy:

  1. Content Hashing: Configure output.filename: '[name].[contenthash].js' for all exposed modules and shared chunks.
  2. Independent Release Cycles: Deploy remotes independently. The host only needs to know the remote’s base URL; it does not require a rebuild when a remote updates.
  3. CDN Cache Invalidation: Purge CDN caches for remoteEntry.js immediately after deployment. Leave hashed chunks untouched to preserve edge cache hits.
  4. Build Performance Tuning: Enable cache: { type: 'filesystem' } and isolate ModuleFederationPlugin cache directories per container to prevent cross-contamination during parallel CI builds.

Common Pitfalls #

Issue Root Cause & Resolution
Mismatched publicPath causing 404 on remoteEntry.js When output.publicPath is hardcoded to / instead of auto or a dynamic CDN URL, the browser requests the remote entry from the host domain, resulting in a 404 and container initialization failure.
React/ReactDOM version duplication breaking hooks Loading multiple React instances across federated boundaries triggers ‘Invalid Hook Call’ errors. Enforcing singleton: true and matching requiredVersion ranges in the shared config resolves this.
CORS blocking remote container initialization Cross-origin requests to remoteEntry.js fail if the remote server lacks Access-Control-Allow-Origin headers. Configure CORS middleware or deploy containers under the same origin to bypass browser security restrictions.
Over-eager sharing increasing initial bundle size Setting eager: true on non-critical shared packages forces them into the initial chunk. Use lazy loading defaults and only mark truly critical dependencies (like React) as eager to optimize TTI.

FAQ #

How do I handle version mismatches between host and remote shared dependencies? Configure strictVersion: true to throw on mismatch, or use requiredVersion with fallback ranges. The host’s shared version takes precedence unless the remote specifies a higher compatible version.

Can Module Federation work across different domains without CORS issues? Yes, but the remote server serving remoteEntry.js must explicitly allow cross-origin requests. Configure Access-Control-Allow-Origin: * or restrict to specific host domains.

What is the recommended deployment strategy for independent container updates? Deploy containers independently with content-hashed filenames. Keep remoteEntry.js stable or use a versioned CDN path to prevent cache poisoning during rolling updates.