Introduction
Cross-MFE communication is the biggest challenge of Micro Frontend. Each MFE runs in its own runtime, without shared memory. This article introduces secure communication patterns.
1. Golden rule
MFEs should be loose coupled — communicate via contracts, not shared state.
❌ Shared Global State:
window.globalStore = { cart: { items: [...] } }
→ Coupling nightmare, race conditions
✅ Event-based Communication:
MFE A publishes: "cart:item-added" { productId, qty }
MFE B subscribes: update cart badge
→ Loosely coupled, observable
2. Communication Patterns
2.1 Custom Events (Browser Native)
// Product MFE - Publish
window.dispatchEvent(new CustomEvent('cart:item-added', {
detail: { productId: '123', name: 'Laptop', price: 999 }
}));
// Cart MFE - Subscribe
useEffect(() => {
const handler = (e) => {
setCartItems(prev => [...prev, e.detail]);
};
window.addEventListener('cart:item-added', handler);
return () => window.removeEventListener('cart:item-added', handler);
}, []);
2.2 Event Bus (Custom)
class EventBus {
private handlers: Map<string, Set<Function>> = new Map();
on<T>(event: string, handler: (payload: T) => void) {
if (!this.handlers.has(event)) this.handlers.set(event, new Set());
this.handlers.get(event)!.add(handler);
return () => this.handlers.get(event)?.delete(handler);
}
emit<T>(event: string, payload: T) {
this.handlers.get(event)?.forEach(h => h(payload));
}
}
export const eventBus = new EventBus();
2.3 URL-based State
/products?category=electronics&sort=price
→ URL là "shared state" tự nhiên: bookmarkable, shareable, no coupling
2.4 Browser Storage
// Shared preferences via localStorage
localStorage.setItem('user:preferences', JSON.stringify({
currency: 'VND', language: 'vi', theme: 'dark'
}));
3. State Ownership Rules
| State | Owner | How MFEs Access |
|---|---|---|
| Auth state | Shell | Context / Events |
| Cart items | Cart MFE | Events |
| Product data | Product MFE | Not shared |
| User prefs | Shell | localStorage + Events |
| URL params | Shell (Router) | URL |
| UI theme | Shell | CSS Variables |
4. Contract-first Communication
// shared-types/events.ts
interface CartItemAddedEvent {
type: 'cart:item-added';
payload: { productId: string; name: string; price: number; quantity: number; };
}
interface CartUpdatedEvent {
type: 'cart:updated';
payload: { itemCount: number; total: number; };
}
5. Best Practices
Mỗi MFE nên:
✅ Fetch dữ liệu riêng (own API calls)
✅ Manage state riêng (local state)
✅ Render independently
✅ Graceful degradation
Mỗi MFE KHÔNG nên:
❌ Import components từ MFE khác
❌ Access DOM của MFE khác
❌ Share runtime state qua window/global
❌ Assume MFE khác đã loaded
Summary
- Custom Events — simple, browser native
- Event Bus — typed, testable
- URL — best for navigation state
- localStorage — user preferences
- Rule: Each MFE manages its own state, communicates via events
Next article: Lesson 15: Design System & Shared UI — Keep UX consistent