Shared Dependencies
When multiple micro frontends are composed into a single page, each one may bundle its own copy of common libraries — React, lodash, a design system. Left unchecked, this leads to bloated bundles, longer load times, and runtime conflicts (e.g., two versions of React fighting over the DOM). Managing shared dependencies is one of the most critical challenges in micro frontend architecture.
The Duplicate Dependency Problem
Section titled “The Duplicate Dependency Problem”Consider three micro frontends on the same page, each bundling React 18 independently:
| Micro Frontend | React Bundle | Other Deps | Total |
|---|---|---|---|
| Product | 42 KB | 85 KB | 127 KB |
| Cart | 42 KB | 60 KB | 102 KB |
| Checkout | 42 KB | 120 KB | 162 KB |
| Page Total | 126 KB | 265 KB | 391 KB |
The user downloads React three times. With shared dependency management, React is loaded once (42 KB), reducing the page total to 307 KB — a 21% reduction from a single optimization.
Strategies
Section titled “Strategies”Externals
Section titled “Externals”Mark shared libraries as externals in your bundler config so they are not included in each micro frontend’s bundle. Instead, the shell or a shared CDN provides them.
// webpack.config.js for a micro frontendmodule.exports = { externals: { react: 'React', 'react-dom': 'ReactDOM', },};The shell loads them via <script> tags:
<script src="https://cdn.example.com/react@18/umd/react.production.min.js"></script><script src="https://cdn.example.com/react-dom@18/umd/react-dom.production.min.js"></script>Pros: Simple, well-understood, works with any bundler. Cons: Requires coordination on versions, uses global scope, limited to UMD builds.
Import Maps
Section titled “Import Maps”A browser-native mechanism for controlling module resolution. Import maps let you redirect bare specifiers to specific URLs without a bundler.
<script type="importmap">{ "imports": { "react": "https://esm.sh/react@18.3.1", "react-dom": "https://esm.sh/react-dom@18.3.1", "design-system": "https://cdn.example.com/ds@4.2.0/index.js" }}</script>Each micro frontend simply imports react and the browser resolves it to the shared URL.
Pros: No bundler plugins required, standards-based, per-page override capability. Cons: Browser support (polyfills needed for older browsers), debugging can be tricky.
Module Federation Shared Config
Section titled “Module Federation Shared Config”Webpack Module Federation provides a shared configuration that handles version negotiation automatically at runtime.
new ModuleFederationPlugin({ name: 'product', shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, },});When multiple micro frontends declare the same shared dependency, Module Federation loads the highest compatible version once and shares it across all consumers.
Pros: Automatic version negotiation, no external script tags, works with the module system. Cons: Tied to Webpack/Rspack ecosystem, runtime overhead for negotiation.
See the Module Federation page for a deeper dive.
Shared Runtime
Section titled “Shared Runtime”A centralized host application provides common libraries as part of its own bundle, and micro frontends import from the host.
Pros: Full control over versions, single source of truth. Cons: Tighter coupling to the host, less autonomy for micro frontend teams.
The Singleton Pattern
Section titled “The Singleton Pattern”Some libraries must exist as a single instance on the page. React is the canonical example — two copies of React cause hooks to break because they maintain separate internal state.
Singleton sharing ensures that regardless of how many micro frontends request a library, only one instance is loaded and shared across all.
With Module Federation:
shared: { react: { singleton: true, // only one copy allowed strictVersion: true, // fail if versions are incompatible requiredVersion: '^18.0.0', },}Without Module Federation, the externals or import map approach naturally enforces singletons since there is only one script loaded.
Version Management
Section titled “Version Management”The hardest part of shared dependencies is version alignment across independently deployed micro frontends.
Strict Version Pinning
Section titled “Strict Version Pinning”All teams agree on exact versions (e.g., react@18.3.1). Simple but rigid — upgrading requires coordinating all teams simultaneously.
Compatible Range Sharing
Section titled “Compatible Range Sharing”Teams specify compatible ranges (e.g., ^18.0.0). The runtime loads the highest available version within the range. Module Federation supports this natively.
Graceful Fallbacks
Section titled “Graceful Fallbacks”When versions are truly incompatible, a micro frontend can fall back to its own bundled copy. This sacrifices some performance for resilience:
shared: { react: { singleton: true, requiredVersion: '^18.0.0', strictVersion: false, // allow fallback to bundled version },}The Sharing vs. Isolation Trade-off
Section titled “The Sharing vs. Isolation Trade-off”| More Sharing | Less Sharing | |
|---|---|---|
| Bundle size | Smaller (deduplicated) | Larger (duplicated) |
| Coupling | Higher (shared version constraints) | Lower (full independence) |
| Autonomy | Reduced (teams must coordinate) | Maximized (teams choose freely) |
| Risk | Shared failure (bad version affects all) | Isolated failure |
The right balance depends on your organization’s priorities. Most teams start by sharing framework-level dependencies (React, Vue, Angular) and a design system, while keeping application-specific libraries isolated.
For related strategies, see performance optimization techniques and versioning patterns across deployments.
Go Deeper in the Book
This topic is covered in depth in Chapter 4 of Micro Frontends Architecture for Scalable Applications, with detailed examples, diagrams, and production-ready patterns.