Blog #38: Micro-frontend Reality Check – When theoretical glory meets harsh reality
The story of 'divide and conquer' with Micro-frontends ending in a chaos of overlapping library versions.
1. The Context
At that time, the Micro-frontend trend was buzzing all over tech forums. I was managing a fairly large SaaS project with 3 main business areas: Dashboard, Report, and Settings. Every time we deployed, it took 20 minutes to build this giant monolith, and just a tiny change in the Settings page could cause a bug on the Dashboard.
"We must separate it!" – I declared boldly in a technical meeting. I convinced the management to let the team spend 2 months transitioning to a Micro-frontend architecture using Module Federation. We dreamed of a paradise where teams could work independently, use different versions of React if they wanted, and deploy anytime without worrying about affecting each other.
Two months later, that paradise became a battlefield of version conflicts and mysterious runtime errors.
2. Foundational Knowledge
Micro-frontend is simply bringing the microservices philosophy of Backend to the Frontend. Instead of a single application, you split it into multiple small applications running in parallel and "embedded" into a main Host Shell.
The hardest part isn't splitting the code, it's Sharing Dependencies. If the Host uses React 18, but Micro-App A uses React 17, the browser has to download both, making the web heavier. Or worse, if two apps fight over a Global State or a global CSS variable, the system collapses.
3. The Specific Problem
We split the system into 4 parts. Everything initiallly ran smoothly. Until we updated the shared component library (UI-Kit) to a new version to support Dark Mode on the Dashboard page.
Suddenly, the Report page (a separate Micro-app) completely broke its layout because it was still using the old version of UI-Kit but was "forced" to use the new CSS variables passed down from the Host.
// Module Federation config sounding very fancy
new ModuleFederationPlugin({
name: "dashboard",
remotes: {
reports: "reports@http://localhost:3002/remoteEntry.js",
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
"ui-kit": { requiredVersion: "^1.0.0" } // This was the beginning of the disaster
},
});
A large-scale system with dozens of teams developing simultaneously made version synchronization a massive administrative burden, instead of liberating Labor as we imagined.
4. How I Handled It
We started by trying to apply "Strict Versioning"—meaning each Micro-app used its own version. But the web loaded significantly slower; users complained.
I used Chrome DevTools to inspect every JS request. I found our web page was loading 3 versions of React and 2 different versions of Styled-components. I started to doubt my choice: "Did I fall into a trend trap?".
My final solution wasn't a high-tech technical one, but a Process. We established a "Core Team" dedicated to managing Shared Dependencies and mandated that sub-teams must upgrade simultaneously whenever there was a major change. An ironic contradiction: we used Micro-frontends to be independent, but ended up creating a more rigid centralized management mechanism than ever.
5. Trade-offs
Micro-frontends bring independent deployment, but they take away from you:
- Consistency: It's very hard to keep UI/UX synchronized across apps.
- Performance: Network resources are consumed by loading
remoteEntry.jsand duplicate libraries. - Debug Complexity: An error occurs in Micro-app A but the cause might lie in the Host configuration.
Self-reflection: Was I over-engineering? Definitely. With a team of about 15 people at the time, a good Monorepo combined with optimized CI/CD would have been more than enough. Micro-frontends should only be used for massive organizations with hundreds of developers.
6. Lesson Learned
I now understand more clearly: Architecture is never free. The price of flexibility is complexity.
- Advice: Never apply Micro-frontends just because they're "hot." Start with a clean Monolith. Only separate when build times or deployment conflicts become an insurmountable barrier.
- Comparison: A well-structured Monolith (Modular Monolith) always runs faster and is easier to maintain than a poorly structured Micro-frontend.
- An example: Juniors often think using Micro-frontends is "high-level." But a true Senior is someone who knows how to convince the boss not to use them when they're not needed.
If I were to go back, I would choose to split the source code but still build and deploy as a single block (Bundled Monolith) instead of splitting at runtime. Sometimes, "old" but stable is better than "new" but fragile.
Notes on disillusionment with flashy architectural trends.
Series • Part 38 of 50