The Ultimate Guide to Micro Frontends: Architecting Scalable Frontend Systems
Share this article
As frontend applications grow in complexity, monolithic architectures often become bottlenecks for teams needing independent release cycles, technological flexibility, and scalable collaboration. Micro frontends—the practice of decomposing frontends into independently deployable units—offer solutions to these challenges. In this comprehensive guide, Andrew Maksimchenko (lead full-stack developer and solutions architect) shares hard-won insights from implementing micro frontends across large-scale projects.
The Case for Decomposition
Micro frontends extend microservice principles to the UI layer:
- Team Autonomy: Teams own specific features (e.g., Search Team: React, Checkout Team: Vue)
- Independent Deployments: Update product listings weekly while deploying CMS pages daily
- Technology Agnosticism: Mix React, Angular, and vanilla JS seamlessly
- Incremental Modernization: Replace legacy code section-by-section
"Think of it like Lego blocks—each self-contained, pluggable, and replaceable without breaking the whole system," Maksimchenko explains.
Method #1: Iframes & Cross-Window Messaging
The Original Micro-Frontend
Despite their reputation, iframes provide strong isolation:
// Parent app communication
iframe.contentWindow.postMessage({ type: 'init', userId: 42 }, '*');
// Child app (iframe) response
window.addEventListener('message', (event) => {
if (event.data?.type === 'searchResult') {
console.log('Results:', event.data.payload);
}
});
✅ Pros:
- Ironclad sandboxing
- Zero dependency conflicts
- Ideal for legacy/third-party integrations
❌ Cons:
- Styling inconsistencies
- Complex communication
- Performance overhead
Use Cases: Payment gateways, embedded analytics, ads.
Method #2: Web Components (Custom Elements + Shadow DOM)
Native Browser Micro-Frontends
Create framework-agnostic UI elements:
class ProductTile extends HTMLElement {
static observedAttributes = ['title'];
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `
<div id="title"></div>
`;
}
attributeChangedCallback(name, _, newValue) {
if (name === 'title') {
this.shadowRoot.getElementById('title').textContent = newValue;
}
}
}
customElements.define('product-tile', ProductTile);
✅ Pros:
- No framework lock-in
- Native browser support
- Style encapsulation via Shadow DOM
❌ Cons:
- Limited state sharing
- Polyfills needed for IE
- Steep integration curve
Use Cases: Design systems, cross-framework widgets, incremental modernization.
Method #3: single-spa – The Meta-Framework
Orchestrating Multiple SPAs
Coordinate React/Vue/Angular apps under one router:
// Root config
import { registerApplication, start } from 'single-spa';
registerApplication({
name: '@shop/products',
app: () => System.import('@shop/products'),
activeWhen: ['/products']
});
start();
✅ Pros:
- Built-in routing/lifecycles
- Framework interoperability
- Lazy loading
❌ Cons:
- Configuration complexity
- No built-in state management
- Client-side rendering only
Use Cases: Enterprise portals, multi-team SPAs, phased migrations.
Method #4: Module Federation (Webpack 5+)
Runtime Code Sharing
Dynamically load components across independently built apps:
// Remote app config (exposes component)
new ModuleFederationPlugin({
name: 'productApp',
filename: 'remoteEntry.js',
exposes: { './ProductTile': './src/ProductTile.jsx' },
shared: { react: { singleton: true } }
});
// Host app usage
const ProductTile = React.lazy(() => import('productApp/ProductTile'));
✅ Pros:
- True runtime integration
- Shared dependency optimization
- No build-time coupling
❌ Cons:
- Webpack dependency
- Version mismatch risks
- Complex error handling
Use Cases: Shared component libraries, dynamic plugin systems, composite applications.
Beyond the Big Four: Ecosystem Tools
- Piral: Framework for portal-based micro frontends
- Luigi: Enterprise-grade shells with RBAC
- Bit: Component-driven development platform
- Import Maps: Browser-native module resolution
Strategic Considerations
Choose your approach based on priorities:
| Method | Isolation | DevEx | Performance | Complexity |
|---|---|---|---|---|
| Iframes | ★★★★★ | ★★☆ | ★★☆ | Low |
| Web Comp | ★★★★☆ | ★★★☆ | ★★★★☆ | Medium |
| single-spa | ★★☆☆☆ | ★★★☆ | ★★★★☆ | High |
| Module Fed | ★★☆☆☆ | ★★★★☆ | ★★★★☆ | Very High |
"There's no universal solution," Maksimchenko concludes. "Iframes excel in security-critical contexts, Web Components deliver framework freedom, single-spa orchestrates complex SPAs, and Module Federation enables unprecedented runtime integration. Your choice must align with team structure, deployment needs, and long-term evolution strategy."
For teams navigating the micro-frontend landscape, this progression—from isolated iframes to dynamically federated modules—offers a roadmap toward scalable, resilient frontend architectures that grow with organizational complexity.