Carlos Cifuentes
Micro-frontends · Communication

How micro-frontends communicate, and what to avoid

Well-cut micro-frontends have little to say to each other. For what remains, there are a few simple mechanisms, ordered here from the loosest coupling to the tightest.

By Carlos Cifuentes · Frontend architect · Updated 2026-10-02

Start by communicating less

The amount of communication between two micro-frontends measures how good the boundary between them is. If two areas exchange messages constantly, they are probably one area, and merging them is cheaper than building a protocol.

Most legitimate needs fall into three groups: open another area's screen, know something global such as the current user, and learn that something happened elsewhere.

1. The URL

Navigation with parameters is the default mechanism. Orders sends the user to /customers/42?tab=payments, and the customers area loads what it needs.

The URL survives reloads, can be shared and tested, and creates no code dependency between teams. The rules are in Routing across micro-frontends.

2. The backend as the source of truth

When two areas show the same business data, each one fetches it from the API. Nothing is shared in the browser. This costs an occasional duplicate request, which HTTP caching can reduce. In return, no area depends on another being loaded.

3. Shared services from the platform

A few things are truly global: the current user, the language, permissions, feature flags. These come from a small platform package, initialized by the host.

In Angular with federation, this is a service in a library that is shared as a singleton. Keep its API narrow and mostly read-only: observables or signals to read, a few methods to write. A shared service that grows a method for every feature has become global state under another name.

4. Custom events

To announce that something happened, the browser already has a mechanism. It works across frameworks, across framework versions and with web components.

// publisher: orders
window.dispatchEvent(new CustomEvent('orders:order-paid', {
  detail: { orderId: '981' }
}));

// subscriber: notifications
window.addEventListener('orders:order-paid', (e) => {
  refreshBadge(e.detail.orderId);
});

5. A small event bus

An event bus is a thin, typed wrapper around the same idea, published as a shared package: publish(name, payload) and subscribe(name, handler) that returns an unsubscribe function.

It adds three things over raw events: type checking of payloads, one catalog of all events, and a place to log traffic during development. In Angular, a singleton service with an RxJS Subject does the job, but only for remotes that share the same library instance. DOM events and BroadcastChannel also reach parts that do not.

Event design rules

  • Name events as facts, with the owner as prefix. orders:order-paid says what happened. A command such as cart:refresh-now tells another team's code what to do, which couples the two.
  • Send identifiers and small values.
  • Treat events as a public API. Document them. Adding fields is safe. Renaming or removing them needs a deprecation period.
  • Assume nobody may be listening. The subscriber's remote may not be loaded. The publisher must not depend on a reaction.
  • Assume the event may have been missed. A remote loaded later must be able to get the current state from the API.
  • Unsubscribe on destroy. Listeners on window outlive components and leak.
  • Expect no reply. If you need an answer, you need a function call. Use a shared service or the API.

What to avoid

Shared mutable global state. A single store that every micro-frontend reads and writes, or objects on window used the same way, brings back the coupling of the monolith without its tooling. Any team can break any other. Release order starts to matter. Nobody can change the shape of the state, because nobody knows who depends on it.

Other patterns that cause trouble:

  • Importing another remote's code directly. That is a build-time dependency between two teams that were meant to be independent.
  • Using localStorage as a message channel inside one page. It is synchronous, untyped and survives after it should have been cleared.
  • Putting tokens or personal data in events. Every script on the page can listen. Session handling is covered in Shared authentication.

Which mechanism for which need

NeedMechanism
Open a screen in another areaURL
Same business data in two areasEach fetches from the API
Current user, language, permissionsShared platform service
Announce that something happenedCustom event or event bus
Talk to a part in another framework or versionCustom events; attributes and properties on a custom element
Talk to content in an iframepostMessage, with origin checks on both sides

Checklist

  1. Is there a written catalog of cross-area events, with an owner for each?
  2. Does every remote still work when loaded alone, with no other remote present?
  3. Do any two areas exchange more than a handful of event types? Review the boundary, and see Team ownership and Conway's law.

More in this section

Planning a migration or a micro-frontend platform?

In a 20-minute call you describe your situation and I tell you how I'd approach it. If it isn't worth doing yet, I'll say so.

Message me on LinkedIn