The Evolution to Micro Frontends
Modern web applications face growing complexity as teams scale. Traditional monolithic frontends create bottlenecks in development velocity and deployment frequency. Micro frontends offer a path to independent deployments and team autonomy, but bring their own challenges.
Traditional integration approaches and their limitations:
- Iframe isolation creates UX fragmentation and accessibility issues
- Build-time integration requires full rebuilds for any change
- Runtime integration lacked proper code sharing mechanisms
- Version mismatches across teams caused runtime conflicts
Webpack's Module Federation changes this paradigm by enabling multiple separate builds to form a single application, where each build acts as a container that can expose and consume code among themselves.
For teams exploring cross-platform development approaches, Module Federation provides a unified web architecture that complements native and hybrid mobile strategies.
Core Concepts and Architecture
Understanding Containers and Exposed Modules
Each build acts as a container and also consumes other builds as containers. This way, each build is able to access any other exposed module by loading it from its container. The remoteEntry.js file serves as the entry point for discovering available modules in a remote container.
Local vs Remote Modules
Local modules are regular modules that are part of the current build. Remote modules are modules that are not part of the current build but are loaded at runtime from a remote container. Loading remote modules is considered an asynchronous operation.
Shared Dependencies and Version Management
Shared modules are modules that are both overridable and provided as overrides to nested containers. They usually point to the same module in each build, such as React, Lodash, or other shared libraries. The requiredVersion option allows specifying version constraints, while singleton ensures only one instance is loaded across all containers.
One of the significant challenges when managing shared dependencies across multiple federated modules is version compatibility. With several teams shipping features independently, ensuring consistent dependency versions becomes critical for application stability.
Key capabilities that address common pain points
Runtime Code Sharing
Share code and dependencies across builds without full application rebuilds, enabling true independent deployment while maintaining runtime cohesion.
Independent Deployments
Deploy individual micro frontends without coordinating with other teams, reducing deployment friction and enabling faster release cycles.
Dependency Deduplication
Automatically share common dependencies like React across containers, preventing duplicate library bundles and reducing page weight.
Lazy Loading
Remote modules load on-demand when needed, improving initial page load performance and reducing unnecessary code execution.
Version Compatibility
Smart version resolution ensures compatible dependencies while allowing flexibility for teams to upgrade on their own schedule.
Component Library Distribution
Build shared component libraries as containers, enabling centralized design system management with independent updates.
Configuration Deep Dive
The ModuleFederationPlugin Configuration
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
app2: 'app2@http://localhost:3002/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
});
Exposing Modules
new ModuleFederationPlugin({
name: 'shared-components',
exposes: {
'./Button': './src/components/Button',
'./Card': './src/components/Card',
'./Header': './src/components/Header',
},
shared: { react: { singleton: true } },
});
Consuming Remote Modules
Remote configurations support both static URLs and dynamic resolution. Dynamic remotes enable runtime-based module selection, useful for A/B testing or version switching.
remotes: {
app1: `promise new Promise(resolve => {
const urlParams = new URLSearchParams(window.location.search);
const version = urlParams.get('app1VersionParam');
const remoteUrl = 'http://localhost:3001/' + version + '/remoteEntry.js';
// Load and resolve remote container
})`,
}
Best Practices for Production
Asynchronous Boundaries and Performance
We strongly recommend using an asynchronous boundary. It will split out the initialization code of a larger chunk to avoid any additional round trips and improve performance in general.
// index.js
import('./bootstrap');
// bootstrap.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
Dynamic Public Path
This approach is particularly helpful when mounting independently deployed child applications on subpaths of the host domain. Remote applications can set their public path dynamically at runtime.
Error Handling and Fallbacks
- Implement error boundaries for remote components
- Provide graceful degradation when remotes fail
- Use loading states to maintain perceived performance
- Monitor remote availability proactively
Build Optimization
- Use chunking strategies that minimize duplicate code
- Implement caching for faster rebuilds
- Optimize CI/CD pipelines for federated builds
- Monitor bundle sizes across all containers
For teams implementing automation in web applications, Module Federation provides the architectural foundation for scalable, maintainable codebases.
Module Federation and User Experience
Maintaining Design Consistency
The container pattern for component libraries enables centralized design system management. Teams can consume shared components while maintaining consistency across the application. Many applications share a common components library which could be built as a container with each component exposed. Changes to the components library can be separately deployed without the need to re-deploy all applications.
- Distribute design tokens through shared modules
- Implement consistent styling approaches
- Share theming configuration across boundaries
- Use versioned component releases for predictability
When building cross-platform applications with modern frameworks, Module Federation complements desktop and web deployment strategies with shared component architectures.
Performance Considerations
Micro frontend architecture affects user experience through:
- Initial load: Remote modules load on-demand, improving first contentful paint
- Bundle size: Dependency deduplication reduces overall page weight
- Runtime performance: Lazy loading keeps the main thread available
- Perceived performance: Loading states maintain engagement during fetches
Each page of a Single Page Application is exposed from container build in a separate build. The application shell is also a separate build referencing all pages as remote modules, so each page can be separately deployed.
Frequently Asked Questions
Sources
- Webpack Module Federation Documentation - Official Webpack documentation covering core concepts, configuration, and troubleshooting
- LogRocket: Solving micro-frontend challenges with Module Federation - Comprehensive guide covering React implementation patterns, challenges, and solutions
- Zalando Engineering: Building a Modular Portal with Webpack Module Federation - Real-world enterprise perspective on shared dependency management