View all articles
MicrofrontendsArquiteturaReact

Microfrontends: I helped split one app into seven. Would I do it again?

When we decided to break our app into microfrontends, I was the one pushing back hardest. Today I'd make the same call, knowing exactly what it costs.

September 24, 2026
7 min read
Leo's cartoon avatar, thoughtful, chin resting on his fist with question marks around him, next to a browser window split into colorful blocks, one showing the Familhão logo and another flying in.

When the idea of breaking our application into microfrontends came up, I was the one pushing back the hardest.

My argument was simple. There weren't that many teams, nothing a good conversation couldn't sort out at deploy time. And turning a monorepo into microfrontends is much easier than going the other way. If we ever needed to, we could split later.

The group decided to split anyway, into around seven microfrontends, using React, single-spa and Module Federation. I took part in the design and implementation, and I owned the Achou Levou module and the shared Design System. The project was Familhão, from Globo, and there's more detail in the portfolio case study.

Today, looking at how big the product got, I'd make the same decision. This post is about why I changed my mind and about the price that comes with it. It's not a tutorial: the single-spa and Module Federation docs do that better.

What microfrontends solve (and what they don't)

Microfrontends aren't a performance technique. They don't make your code cleaner. They don't fix badly written components. If the app is a mess, you end up with seven messy apps, with the bonus that now they have to talk to each other.

What they solve is a business problem: several teams working on the same product, needing to ship fast without asking each other for permission.

That was our case. Several teams were working at the same time on very different areas: login, payments, the Familhão area, the Achou Levou offers. Each at its own pace. The main pain was deployment: a single bottleneck for everyone and little independence between teams.

If the problem had been the code, microfrontends would have been the wrong answer. It was an organizational problem.

What we ended up with

The structure was fairly classic:

  • a host that loads and orchestrates the modules;
  • around seven microfrontends, each owning an area of the product or a specific function;
  • a shared Design System;
  • a shared-state API for whatever had to cross module boundaries.

We used single-spa and Module Federation together, which sometimes confuses people, because both show up in tutorials as "the way to do microfrontends". They solve different parts of the problem:

  • single-spa handled orchestration: which app mounts on which route, each app's lifecycle, mounting, unmounting and keeping the modules living together.
  • Module Federation handled runtime loading and sharing: dependencies like React, components and common resources, without each module shipping its own copy.

The combination was a decision made by the architecture group I was part of. The idea was to use single-spa purely as the orchestration layer, without also making it responsible for sharing dependencies and modules. Module Federation was a better fit for that.

The decision most worth explaining: shared state

Some state had to exist for everyone: the logged-in user, the session, journey data, signals from one module to another.

The obvious path is a shared global store. Redux in the host, everyone imports it, done. It's simple on day one and a problem by day ninety, because every microfrontend now depends on the same library, the same version and the same state shape. You've just recreated the coupling the architecture was supposed to break.

We went another way: an in-house API to read and update that state. Under the hood it was Zustand, but no module needed to know that. Modules talked to the API. If the internal implementation changed, nobody noticed.

The price: one more layer to maintain, and an annoying discipline about what is really global.

A hypothetical example of how this gets away from you: the payments team needs to know whether the user has already seen a promo banner. Adding a "saw the banner" field to shared state takes five minutes. Six months later, three modules depend on it, nobody remembers who added it, and changing its shape turns into a negotiation between teams. Every field in global state is a contract. Before adding one, it's worth asking whether more than one module really needs it.

What hurt

This is the part microfrontend talks tend to rush through. Some of these problems I lived through. Others were only starting when I left the project.

Initial load. Each microfrontend is one more thing to fetch over the network before the screen shows up. Our strategy was to load by route, on demand: users only download the module for the area they're entering. That solves a good part of it, but the first visit to each area still pays the cost of fetching that module.

Navigation. To the user it's one product. To the code, a route change can mean unmounting one app and mounting another. Making that feel natural takes work.

Visual consistency. This is where the Design System, one of my responsibilities, paid off. It was distributed as an npm package built with webpack, and teams didn't push back on adopting it. The real work was cultural: understanding what belonged in the Design System, building components for reuse and, above all, accepting that a Design System component has no business logic. It's just structure and style. The logic lives in the microfrontend that uses it.

With several teams shipping on their own, consistency can't depend on goodwill. It has to live inside the component everyone uses.

CSS. Seven apps on the same page could easily fight over styles. In our case this barely hurt: almost everything was styled with Tailwind directly in the components, with very little global CSS to clash.

Duplicated bundles. Whatever isn't shared gets duplicated. Without care, each module brings its own copy of utility libraries and users download the same thing several times.

Shared versions. With Module Federation you declare shared dependencies (React is the obvious one) and you usually want a single instance of them on the page. In practice, teams have to agree on versions. Full autonomy to upgrade dependencies doesn't exist; what you get is negotiated autonomy. I left the project before this became a problem, but it's the kind of bill that shows up over time, when each team wants to upgrade at its own pace.

None of these problems is impossible to solve. But they all stop being a single screen's problem and become an architecture problem. And someone has to own them.

When I wouldn't use them

It's funny rereading this list, because the first item is basically the argument I was making back then.

I'd be suspicious of microfrontends when:

  • there's a single team, or a few teams that can still sort out deploys in a conversation;
  • the motivation is "organizing the code" (that's what modules, well-thought-out folders and monorepos are for);
  • nobody is going to own the platform: the host, the contracts and the shared versions;
  • the product is still figuring out what it is, and the boundaries between areas change every month.

In those scenarios, a well-organized monorepo gives you most of the organization without charging the price of runtime integration. And if things change, splitting later is easier than merging.

So, would I do it again?

Yes.

Back then I didn't see much value in it. There weren't so many teams that a conversation couldn't sort out deploys, and I thought it was wiser to start with a monorepo and split only when it hurt.

Looking at how big the product got and the backlog that built up, I'd choose microfrontends again. My read was right for that moment and wrong for what came next.

Even knowing that, in practice, the projects weren't all that micro. What they gave each team was autonomy and development speed, and that was worth the cost.

In the end, microfrontends are a decision about how teams work that happens to have a technical implementation. And sometimes they're also a bet on how many teams you'll have a year from now.