Micro Frontends · How it works
2 min readRapid overview
How it works
Core Concepts
What Are Micro Frontends?
- Independent Deployment: Each micro frontend can be deployed independently
- Technology Agnostic: Teams can choose different frameworks (React, Angular, Vue, etc.)
- Team Ownership: Each team owns a specific business domain end-to-end
- Isolated Development: Teams work in isolation without coordination overhead
- Incremental Upgrades: Update parts of the application without rewriting everything
When to Use Micro Frontends
✅ Good Use Cases:
- Large applications with multiple teams
- Applications that need technology diversity
- Long-lived applications requiring gradual modernization
- Organizations with independent business domains
- Teams that need to scale independently
❌ Not Recommended For:
- Small applications with a single team
- Applications requiring tight integration
- Performance-critical applications (overhead considerations)
- Short-lived projects
Implementation Approaches
1. Build-Time Integration
Micro frontends are composed at build time using package management.
{
"name": "shell-app",
"dependencies": {
"@company/header-mfe": "^1.2.0",
"@company/product-mfe": "^2.1.0",
"@company/checkout-mfe": "^1.0.5"
}
}
Pros:
- Simple to implement
- Good performance (single bundle)
- Type safety across boundaries
Cons:
- All micro frontends must be deployed together
- Difficult to have independent deployments
- Versioning can be complex
2. Runtime Integration via iframes
Each micro frontend runs in its own iframe.
<div class="app-container">
<iframe src="https://header.example.com" />
<iframe src="https://products.example.com" />
<iframe src="https://footer.example.com" />
</div>
Pros:
- Complete isolation (CSS, JS, global state)
- Easy to implement
- Technology agnostic
Cons:
- Performance overhead
- Difficult routing and deep linking
- Complex communication between frames
- Poor UX (scrolling, responsiveness)
- SEO challenges
3. Runtime Integration via JavaScript
Micro frontends are loaded dynamically at runtime.
// Shell application
const loadMicroFrontend = async (name, host) => {
const script = document.createElement('script');
script.src = `${host}/remoteEntry.js`;
script.onload = () => {
window[name].init({
sharedDependencies: {
react: React,
'react-dom': ReactDOM
}
});
};
document.head.appendChild(script);
};
await loadMicroFrontend('headerMFE', 'https://header.example.com');
await loadMicroFrontend('productMFE', 'https://products.example.com');
Pros:
- Independent deployments
- Runtime composition
- Shared dependencies
Cons:
- More complex setup
- Runtime overhead
- Version management complexity
4. Web Components
Use web components as a standard for creating micro frontends.
// Product micro frontend
class ProductCatalog extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host { display: block; }
.product { padding: 1rem; }
</style>
<div class="product-catalog">
<h2>Products</h2>
<div id="products"></div>
</div>
`;
this.loadProducts();
}
async loadProducts() {
const products = await fetch('/api/products').then(r => r.json());
this.renderProducts(products);
}
renderProducts(products) {
const container = this.shadowRoot.querySelector('#products');
container.innerHTML = products.map(p => `
<div class="product">${p.name} - $${p.price}</div>
`).join('');
}
}
customElements.define('product-catalog', ProductCatalog);
Usage:
<product-catalog></product-catalog>
Pros:
- Standards-based
- Great encapsulation
- Framework agnostic
- Native browser support
Cons:
- Learning curve
- Limited framework integration
- SSR challenges
5. Module Federation (Webpack 5+)
Share code and dependencies across micro frontends at runtime.
// webpack.config.js for Header MFE
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'header',
filename: 'remoteEntry.js',
exposes: {
'./Header': './src/Header'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})
]
};
// webpack.config.js for Shell App
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
header: 'header@https://header.example.com/remoteEntry.js',
products: 'products@https://products.example.com/remoteEntry.js'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})
]
};
// Usage in Shell App
import React, { lazy, Suspense } from 'react';
const Header = lazy(() => import('header/Header'));
const Products = lazy(() => import('products/ProductList'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading header...</div>}>
<Header />
</Suspense>
<Suspense fallback={<div>Loading products...</div>}>
<Products />
</Suspense>
</div>
);
}
Pros:
- True runtime integration
- Shared dependencies
- Independent deployments
- Built into Webpack 5
Cons:
- Webpack-specific
- Complex configuration
- Debugging can be difficult
Real-World Examples
- Spotify: Uses micro frontends for different sections
- Zalando: Implements micro frontends for their e-commerce platform
- IKEA: Uses micro frontends for product catalogs
- SoundCloud: Implements different features as micro frontends