How teams go from copy-paste chaos → internal npm packages → a monorepo that finally makes sense.
⚡ TL;DR — Micro frontends give teams autonomy, but create a hidden code sharing problem. Internal npm packages help, but carry publishing overhead. A monorepo gives you reuse with a fraction of the maintenance cost.
🧩 The Scale Problem with Micro Frontends
As frontend applications grow, teams break large apps into micro frontends — so different parts of the product can evolve independently.
In an ecommerce platform, that might look like:
| App | Responsibility |
|---|---|
| 🔐 Auth | Login, signup, session |
| 🛍️ Product Catalog | Listings, search, details |
| 🛒 Cart & Checkout | Cart state, payments |
| 👤 Account / Settings | Profile, preferences |
This works great for team autonomy — different teams own, ship, and evolve their pieces without blocking each other. 🚀
But as the number of MFEs grows, a problem quietly emerges:
⚠️ The hidden cost: Even though apps are split, they still rely on the same shared building blocks — formatters, API clients, hooks, types, business logic. Managing that shared code across repos is harder than it looks.
The shared building blocks typically include:
// Common code every MFE needs
price & currency formatters
API request helpers
shared product and user types
auth / session utilities
analytics tracking helpers
reusable hooks // useCart(), useAuth(), useDebounce()
business logic // discounts, tax, inventory checks
This is where things start getting messy. Teams solve it in one of two ways — and both have downsides.
📦 The Polyrepo + npm Package Approach
In a polyrepo setup, apps live in separate repositories:
auth-app/
product-catalog-app/
cart-checkout-app/
account-app/
To avoid duplication, shared code gets extracted into internal packages:
shared-ui/
shared-hooks/
shared-utils/
shared-types/
api-client/
✅ This is definitely better than copy-paste. Shared code is reusable and has clean boundaries.
The Release Friction Problem
But over time, a different kind of overhead creeps in. Even a small shared change now requires this entire workflow:
1. Update the shared package
↓
2. Raise a PR & get it reviewed
↓
3. Publish a new version to npm
↓
4. Bump dependencies in all consuming apps
↓
5. Retest each app independently
↓
6. Release everything separately
😓 That’s a lot of process for changing one helper or one hook. As shared code grows, teams end up maintaining not just apps — but a whole ecosystem of internal packages. Code sharing starts feeling heavier than it should.
🏗️ What Is a Monorepo?
A monorepo is simply a setup where multiple apps and shared packages live inside the same repository.
apps/
auth-app/
product-catalog-app/
cart-checkout-app/
account-app/
packages/
shared-ui/
shared-hooks/
shared-utils/
shared-types/
api-client/
Apps consume shared code cleanly — just like a regular package, except there’s no publish step:
import { Button, Modal } from '@repo/shared-ui'
import { useCart, useAuth } from '@repo/shared-hooks'
import { formatCurrency } from '@repo/shared-utils'
import { apiClient } from '@repo/api-client'
import type { Product } from '@repo/shared-types'
💡 Key insight: You no longer need to publish every shared abstraction as an npm package just to reuse it. That single change transforms the developer experience.
✅ How Monorepo Solved the Problem
Once teams move to a monorepo, shared code finally has a natural home. Instead of forcing every reusable piece into a separately published package, code is shared locally through internal workspaces.
The New Workflow
1. Update the shared code
↓
2. All apps use it immediately
↓
3. Test everything together
↓
4. Ship in one PR ✅
That gives teams the best of both worlds:
✨ Shared like packages. Managed like one system. Modular code organisation, clean boundaries, reuse across apps — without the publishing and versioning overhead.
Now when a shared utility, hook, or API client changed:
- All consuming apps could use it immediately
- Changes could be tested together
- Refactoring became easier
- Version drift disappeared
🎯 What Teams Gain
The biggest win isn’t just cleaner code — it’s less friction in day-to-day development.
🧹 Less Duplication
Shared code has a clear, single place to live. No more copy-paste divergence.
⚡ Faster Development
No more publish-bump-retest cycles for every small shared change.
🔧 Easier Refactoring
Shared code and its consumers can evolve together, atomically.
🎯 Better Consistency
All apps stay aligned on the same implementation, always.
📉 Lower Overhead
No more maintaining a growing ecosystem of tiny internal package repos.
🔍 Better Visibility
One place to search, one place to review, one place to understand the system.
☝️ One Important Lesson
Monorepo is powerful — but only when sharing is intentional, not automatic.
Only extract code into a shared package when it is:
- ✅ Used by two or more apps (not just one)
- ✅ Valuable to keep consistent across the system
- ✅ Stable enough to centralise without frequent churn
📌 If code is specific to one app, keep it local. Over-extraction creates a different kind of mess — a shared package that nobody quite owns. Intentional sharing is what keeps a monorepo healthy.
📊 Quick Comparison
| Approach | Pros | Cons |
|---|---|---|
| Copy-paste | Fast initially | Duplication, bugs multiply, inconsistency |
| Internal npm packages | Reusable, modular | Publishing overhead, version drift, slow iteration |
| Monorepo workspaces | Reusable + fast iteration + atomic refactors | Needs discipline, initial setup cost |
💭 Final Thoughts
“The best architecture isn’t the one with the most packages — it’s the one with the least friction.”
Micro frontends solve real scaling problems. But as they grow, your code sharing strategy matters just as much as your app boundaries.
A monorepo ends up being the missing piece for most teams — it gives package-style modularity without the publishing overhead, and that makes a huge difference in everyday developer experience. 🚀