Skip to content
All articles
Micro-FrontendsArchitecture

Micro-Frontends Are Not Always the Answer

After migrating to micro-frontends at CARS24, here is what worked, what failed, and when you should avoid them entirely.

28 February 202611 min read

The Allure of Micro-Frontends

The pitch is compelling: independent teams, independent deployments, technology flexibility, and no more monolith coordination. When CARS24 decided to adopt micro-frontends, I was genuinely excited. We had six frontend teams stepping on each other's toes in a massive Next.js monolith, and the deploy queue was a constant source of friction.

What I didn't appreciate at the time was the sheer volume of new complexity micro-frontends introduce. We traded coordination problems for infrastructure problems, and infrastructure problems are harder to debug at 2 AM.

What Worked: Team Autonomy

The biggest win was deployment independence. Once the infrastructure was in place, each team could deploy their micro-frontend without coordinating with anyone else. Our deploy frequency went from 2-3 times per day for the whole app to 5-10 times per day per team. Feature velocity increased noticeably.

Team ownership improved dramatically. When a team owns a micro-frontend end-to-end — routing, state, API calls, UI — they move faster and take more pride in quality. The car listing team shipped a complete redesign in two weeks because they didn't have to worry about breaking the checkout flow.

Technology migration also became feasible. One team upgraded to React 18 a full month before the others, without any risk to the rest of the platform. This kind of incremental modernization is nearly impossible in a monolith.

What Failed: Shared State and Consistency

The first major pain point was shared state. User authentication, shopping cart state, and navigation were used by every micro-frontend. We tried a shared state library published as an npm package, but version drift between micro-frontends caused subtle bugs that were nightmarish to debug.

Visual consistency was another casualty. Despite having a shared design system, each team made slightly different choices about spacing, animation timing, and interaction patterns. The app started to feel like six different products stitched together. Users noticed. Our NPS score for 'app feels cohesive' dropped by 12 points.

We eventually solved the state problem with a custom event bus using the BroadcastChannel API, but it took three iterations and six weeks of engineering time. If I'd known the cost upfront, I would have pushed for a different architecture.

The Performance Tax

Each micro-frontend brought its own copy of React, its own CSS-in-JS runtime, and its own utility libraries. Despite our best efforts with module federation and shared dependencies, the total JavaScript payload increased by 35% after the migration.

Page transitions between micro-frontends were noticeably slower than in-app navigations within a single micro-frontend. The shell application had to unmount one micro-frontend and mount another, which introduced a visible loading state that didn't exist in the monolith.

We mitigated this with aggressive prefetching and by sharing React as a singleton through module federation. But it took months of optimization work to get back to the performance baseline we had with the monolith. That's months of engineering effort spent running in place.

When You Should Use Micro-Frontends

After living with this architecture for over a year, I believe micro-frontends make sense only when you have more than four independent teams working on the same product, a monolith deployment pipeline that is a proven bottleneck, and the engineering bandwidth to build and maintain the infrastructure layer.

If you have fewer than four teams, a well-organized monolith with clear module boundaries and a good CI/CD pipeline will serve you better. The coordination cost is lower than the infrastructure cost of micro-frontends.

If you're a startup or a small team considering micro-frontends, I would strongly advise against it. You're optimizing for a scaling problem you don't have yet, and you're paying the complexity cost today.

What I Would Do Instead

If I were starting over at CARS24, I'd keep the Next.js monolith but invest heavily in module boundaries, code ownership via CODEOWNERS, and a fast CI pipeline with parallelized builds. I'd use a monorepo tool like Turborepo to give teams fast feedback loops without the operational overhead of separate deployments.

The problem we were trying to solve — team autonomy and fast deployments — has simpler solutions than micro-frontends. We reached for the most complex tool in the toolbox when a wrench would have sufficed.

Found this useful? I write about engineering, performance, and career growth.