Framework Agnostic Communication Patterns for Micro Frontends

Posted on Oct 15, 2025

Connect React, Vue, Angular (and more) micro-frontends with pure browser APIs—no framework lock-in, zero overhead. Modern micro-frontend architectures often mix diverse technologies on the same page. When a user adds an item to the cart in a React-based Product Catalog, how does a Vue-powered Shopping Cart pick up that change? Traditional, framework-specific solutions simply don’t translate across boundaries. This post explores five core, framework-agnostic communication patterns—plus additional techniques—to glue your micro-frontends together without tying them to any one technology.


1. Window CustomEvents: The Universal Language

CustomEvents are native to every browser and work in any framework.

Publish an event:

const event = new CustomEvent('cart:itemAdded', {
  detail: { productId: 'prod-123', name: 'Laptop', price: 999 }
});
window.dispatchEvent(event);

Subscribe to events:

const handler = e => { console.log('Item added:', e.detail); };
window.addEventListener('cart:itemAdded', handler);
// Remove listener on unmount
window.removeEventListener('cart:itemAdded', handler);

Use cases

  • E-commerce: Product Catalog → Shopping Cart
  • Auth: Broadcast login/logout
  • Theme: Apply dark/light modes
  • Search: Emit filter or query updates

Learn more: CustomEvent · addEventListener


2. Custom Message Bus: Structured Pub/Sub

For more sophisticated workflows, a shared message bus offers organization and debugging.

// message-bus.js
class MessageBus {
  constructor() { this.subs = {}; }
  publish(event, data) { (this.subs[event] || []).forEach(h => h(data)); }
  subscribe(event, handler) {
    this.subs[event] = this.subs[event] || [];
    this.subs[event].push(handler);
    return () => {
      this.subs[event] = this.subs[event].filter(h => h !== handler);
    };
  }
}
window.messageBus = new MessageBus();

Publish

window.messageBus.publish('user:loggedIn', { userId: 42 });

Subscribe

const unsubscribe = window.messageBus.subscribe('user:loggedIn', data => {
  console.log('User logged in:', data);
});
// Call unsubscribe() when done

Use cases

  • Sessions: Notify MFEs when users log in/out
  • Feature flags: Toggle features at runtime
  • Workflows: Orchestrate processes (e.g., checkout)
  • Analytics: Centralize event tracking

3. BroadcastChannel: Real-Time Cross-Tab

Keep multiple tabs in sync with the BroadcastChannel API.

const channel = new BroadcastChannel('app_sync');

// Listen
channel.onmessage = ({ data }) => {
  if (data.type === 'LOGOUT') window.location.href = '/login';
};

// Send
channel.postMessage({ type: 'LOGOUT' });

// Cleanup
channel.close();

Use cases

  • Logout: Close sessions in all tabs
  • Cart: Sync across tabs
  • Theme: Reflect changes everywhere
  • Collaboration: Presence/status updates

Learn more: BroadcastChannel


4. Global Window Object: Simple Shared State

Attach a shared object to window for straightforward state or utility sharing.

// Initialize in shell
window.__APP_STATE__ = {
  user: null,
  theme: 'light',
  setUser(user) {
    this.user = user;
    window.dispatchEvent(new CustomEvent('user:changed', { detail: user }));
  }
};

Access or update from any micro-frontend

const user = window.__APP_STATE__.user;
window.__APP_STATE__.setUser({ name: 'Alice' });

Use cases

  • Config: Global settings or feature flags
  • User: Provide context (ID, roles)
  • Utils: Common helpers (date formatting)
  • Localization: Store locale/translations

5. LocalStorage Events: Persistent Cross-Tab

Combine localStorage with the storage event for persistence and cross-tab updates.

// Update cart
localStorage.setItem('cart', JSON.stringify(cart));

// Listen in other tabs
window.addEventListener('storage', e => {
  if (e.key === 'cart') {
    console.log('Cart updated from another tab:', JSON.parse(e.newValue));
  }
});

Use cases

  • Cart: Persist and sync
  • Drafts: Autosave forms
  • Prefs: Store user settings
  • Offline: Cache data

Learn more: localStorage · storage


Additional Framework-Agnostic Techniques

  • URL Hash Changes: Listen for hashchange (MDN)
  • History API: Use pushState/popstate (MDN)
  • postMessage: Window messaging (MDN)
  • IndexedDB: Complex data storage (MDN)
  • SharedWorker: One worker for all contexts (MDN)
  • ServiceWorker.postMessage: Worker messaging (MDN)
  • MutationObserver: Watch DOM changes (MDN)
  • Event Bus Libraries: Pure JS pub/sub (e.g., pubsub-js)
  • DOM Attributes/Data Attributes: Shared element data
  • Hidden DOM Elements: Off-screen data carriers

Choosing the Right Pattern

Pattern Same-Page Cross-Tab Persistent Complexity
CustomEvents Low
Message Bus Medium
BroadcastChannel Low
Window Object Very Low
LocalStorage Low

Final Tips

  • Namespace your events (e.g., cart:itemAdded, user:changed)
  • Always clean up listeners to prevent memory leaks
  • Handle errors gracefully within handlers
  • Use TypeScript for event payload definitions

These patterns ensure your micro-frontends remain truly framework-agnostic, flexible, and maintainable across diverse technology stacks.