Our React app started like a puppy 🐶. Cute, small, easy.
Five years later, it was a Great Dane chewing through our furniture 🪑.
Every feature slowed us down.
Every release felt risky.
The “quick fixes” piled up into tech debt.
We needed a way to break it apart without hitting “rewrite from scratch.”
Micro-frontends became our path.
Here’s how we split a monolith React app into micro-frontends, the pitfalls we hit, and the lessons we’d pass on.
📖 A Quick Story
When we pitched micro-frontends to leadership, the promise sounded perfect ✨: independent teams, faster deployments, cleaner boundaries.
Reality check:
Routing chaos,
Dependency drift,
UI looking like a patchwork quilt 🎨.
And laptops screaming during local dev.
Still, breaking apart the monolith taught us valuable lessons worth sharing.
🗂️ Step 1: Define the Boundaries
You can’t just slice at random. We first mapped the app’s domains: auth, dashboard, checkout, settings. These became our candidate micro-frontends.
Tip: Slice by business capability , not by components. A “header micro-frontend” is a recipe for pain.
🛣️ Step 2: Routing the Split
The monolith’s react-router setup assumed one app. Micro-frontends demand a single “shell” that delegates routes.
Example:
<BrowserRouter>
<Routes>
<Route path="/dashboard/*" element={<DashboardApp />} />
<Route path="/checkout/*" element={<CheckoutApp />} />
</Routes>
</BrowserRouter>
Pitfall : Overlapping routes caused chaos until we defined clear ownership of paths.
🔗 Step 3: Handling Shared State
Our monolith had one Redux store. Duplicating that across apps = trap.
We isolated state per domain, then created a shared session store for auth.
Example:
import { createStore } from 'zustand';
export const useSession = createStore((set) => ({
user: null,
setUser: (user) => set({ user })
}));
Lesson: Share as little state as possible. Every extra slice ties teams back together.
⚙️ Step 4: Splitting the Build
We used Webpack Module Federation to load micro-frontends at runtime. Each team owned its build, but the shell wired them together.
Example:
new ModuleFederationPlugin({
name: 'checkout',
filename: 'remoteEntry.js',
exposes: {
'./CheckoutApp': './src/App',
},
});
Pitfall: Version drift ⚠️. If one app shipped React 18 and another React 17 → runtime errors.
🎨 Step 5: Tackling UI Consistency
Once split, each team styled differently. The UI looked like a collage 🖼️.
Fix: We built a shared design system package. Components were pulled live, not copied.
💻 Step 6: Local Development Without Tears
At first, running the shell + every micro-frontend crushed laptops 🖥️🔥. Coffee cooled while builds spun.
Solution: Built mocks and stubs. Local dev usually meant one micro-frontend + stubbed neighbors.
🗺️ The Roadmap We’d Recommend
Map domains first — avoid atom-sized micro-frontends.
Centralize routing — clear ownership of URL paths.
Keep shared state minimal — only what must be global.
Align dependencies early — React versions, styling, libraries.
Adopt a design system early — stops UI drift.
Stub for local dev — save your laptop (and sanity).
📌 Practical Takeaways
Micro-frontends are surgery 🩺, not a silver bullet.
Split by business capability , not UI pieces.
Align on dependencies + design systems early.
Test local dev workflows before scaling to teams.
🎁 Something Extra (Resources)
🙌 More Like This? Let’s Connect!
📲 Follow me for more dev tips, tools, and trends!
- 📸 Instagram: @tahamjp
- 🧠 Dev.to: @tahamjp
- 🐦 X.com: @tahamjp
- 💬 Telegram Channel: The Intelligent Interface
🔑 Interface Insider (exclusive): Join the community – share, learn, and collaborate with other members!
Check out my latest dev articles and tutorials — updated weekly!
Let’s keep building cool stuff 🚀
Top comments (3)
A very practical guide to taming a React monolith. Your emphasis on splitting by business capability, not components, is the crucial takeaway many teams miss.
Thanks! Yeah, that distinction made a huge difference for us — organizing by capability keeps things way saner over time.
🙌 Thanks for reading! Follow me for more front-end tips 💡