Asher Cohen
Back to posts

Rebuilding Frontends From Raw experience

The truth behind microfrontends

I didn’t adopt microfrontends because they were trendy. I adopted them because a monolithic frontend eventually backed me into a corner where builds were slow, deploy cycles dragged, shared state felt brittle, and multiple teams collided in the same codebase. At some point you stop debating architecture and start asking a simpler question about the physics of the problem. The browser only guarantees a few fundamentals: it loads documents in isolation through iframes, it loads modules natively through ES Modules, and it can fetch JavaScript at runtime through mechanisms like module federation. Everything else from frameworks to bundlers and monorepos is optional. When viewed this way microfrontends stop feeling novel and start feeling like the natural consequence of how browsers already work.

alt text

Once I began stripping away assumptions, things became clearer. We assume we need one framework, one repo, one deployment, or one shared global state, yet the browser demands none of this. Removing these assumptions made the architecture more honest. Iframes offered strong isolation and predictable failure boundaries. ES Modules allowed minimal overhead and native integration. Module Federation provided dynamic runtime composition without a central monolith. These are not inventions but extensions of what the platform already supports.

Module Federation: https://webpack.js.org/concepts/module-federation/

ES Modules: https://tc39.es/ecma262/#sec-modules

iframe isolation: https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element

When you decompose a frontend into its fundamentals you get the UI surface, routing, deployment units, communication channels, state boundaries, and design tokens. Microfrontends simply ask you to treat each element deliberately rather than coupling them by accident. This decomposition also makes the ideal design obvious when cost is ignored. In that ideal world each team ships independently, all deployments are safe by default, no one shares accidental globals, and any part of the system can evolve on its own timeline. The integration layer stays thin and stable instead of dictating every detail. What surprised me was not the ideal itself but how attainable it actually is.

alt text

The minimum viable breakthrough is smaller than most teams expect. You prove the architecture by shipping a single independently deployed UI module loaded at runtime. When routing and communication and design tokens remain stable across that boundary you’ve already validated the microfrontend model. Everything after that is scaling the same pattern. This incremental approach also surfaces the common failure modes early. Most microfrontend efforts fail due to drifting contracts, hidden coupling, shared dependency chaos, inconsistent UX, heavy integration shells, or fuzzy security boundaries. These failures share a root cause: weak contracts. The fixes are predictable too, relying on simple versioned APIs, event-driven communication, strict design tokens, and strong runtime isolation.

Design tokens: https://design-tokens.github.io/community-group/format/

Once I stopped assuming that a frontend must be a single mega application everything changed. A microfrontend became a product with its own lifecycle rather than a slice of a larger one. The integration layer became orchestration rather than compilation. This shift made the architecture feel lighter because each part could focus on its responsibilities without central entanglement.

It helps to separate what is physically impossible from what only feels impossible. Zero-latency shared state across independent apps is physically impossible. But multiple frameworks coexisting, independent deployments, dynamic routing, runtime module loading, or strong isolation with smooth UX are all entirely feasible. Most fears around microfrontends are emotional rather than technical.

If I were starting fresh today I would build a thin shell for routing and wiring, then load independently deployed modules via ES Modules or Module Federation. Communication would be event-driven and design tokens would anchor consistency. No shared runtime unless absolutely required. The resulting system is simpler and more adaptable and no longer held back by invisible assumptions. The largest hidden constraint in most teams is the belief that deploys must be coordinated. Removing that single assumption is often enough to unlock autonomy, speed, and resilience.

alt text

After years of working with this approach I’ve learned that the greatest leverage point is always the same: a minimal and stable integration contract built around routing, events, and design tokens. With those boundaries in place everything else is free to evolve. In the end microfrontends matter because they align with the real constraints of browsers and the real constraints of teams. They convert a monolithic risk surface into isolated units that scale independently in complexity and traffic.

That’s why I use them. Not for elegance but for physics.