From Monorepo to Polyrepo: Scaling Micro-Frontends Across Teams
Introduction
In my previous article, I explored building scalable applications with micro-frontends using a monorepo approach powered by Nx. While that approach works incredibly well for small-to-medium teams with shared ownership, I've since explored a polyrepo architecture that better addresses enterprise-scale challenges.
In this article, I'll share why I made the switch, the trade-offs involved, and real-world implementation details from a production deployment.
🎯 The Journey: From Monorepo to Polyrepo
Previous Approach: Monorepo
In the monorepo setup:
- ✅ Everything lives in one repository
- ✅ Nx manages dependencies and builds
- ✅ Shared libraries are TypeScript path aliases
- ✅ Easy local development
- ✅ Atomic commits across multiple MFEs
Example Structure:
mfeworld/ # Single repository
├── apps/
│ ├── dashboard/ # Host application
│ ├── products/ # Remote MFE
│ ├── cart/ # Remote MFE
│ ├── orders/ # Remote MFE
│ └── profile/ # Remote MFE
├── libs/
│ └── shared/
│ ├── layout/ # Shared components
│ ├── services/ # Shared services
│ └── utils/ # Shared utilities
└── nx.json
This worked great for:
- Small teams (< 20 developers)
- Shared ownership model
- Rapid prototyping
- Consistent tooling and dependencies
New Approach: Polyrepo
Now, each micro-frontend and the shared UI Kit live in separate repositories:
📦 Host Repository
├── https://github.com/hemantajax/mfe-host
📦 Shared UI Kit Repository
├── https://github.com/hemantajax/mfe-uikit
└── Published as: @hemantajax/mfe-uikit
📦 Remote MFE Repositories (8 separate repos)
├── products/ → https://hemantajax.github.io/mfedemos/products/
├── cart/ → https://hemantajax.github.io/mfedemos/cart/
├── profile/ → https://hemantajax.github.io/mfedemos/profile/
├── orders/ → https://hemantajax.github.io/mfedemos/orders/
├── analytics/ → https://hemantajax.github.io/mfedemos/analytics/
├── notifications/ → https://hemantajax.github.io/mfedemos/notifications/
├── messages/ → https://hemantajax.github.io/mfedemos/messages/
└── admin/ → https://hemantajax.github.io/mfedemos/admin/
🔗 Live Demo - See it in action!
🤔 Why Polyrepo? The Business Case
1. Team Autonomy & Ownership 🎯
Problem in Monorepo:
- All teams share the same repository
- PR bottlenecks when multiple teams work simultaneously
- Difficult to enforce team boundaries
- Shared CI/CD pipeline can block deployments
Polyrepo Solution:
# Products team owns their repo completely
products/
├── .github/workflows/ # Their own CI/CD
├── src/ # Their code
└── package.json # Their dependencies
# Cart team owns their repo
cart/
├── .github/workflows/ # Independent CI/CD
├── src/ # Their code
└── package.json # Their dependencies
Each team can:
- ✅ Set their own release schedule
- ✅ Choose their own tooling (within agreed standards)
- ✅ Deploy independently
- ✅ Control their own PR process
2. Independent Deployment Pipeline 🚀
Monorepo Challenge:
# Single pipeline for all apps
build-all:
- Build: dashboard, products, cart, orders... (15+ minutes)
- Test: All apps
- Deploy: If ANY app fails, ALL deployments block
Polyrepo Advantage:
# Products team deploys independently
products-pipeline:
- Build: products only (3 minutes)
- Test: products only
- Deploy: products to CDN
- ✅ No impact on other teams
# Cart team deploys at the same time
cart-pipeline:
- Build: cart only (2 minutes)
- Test: cart only
- Deploy: cart to CDN
- ✅ Completely independent
Real-world Impact:
- Deployment frequency increased from 2-3 times/week to 10+ times/day
- Build time per MFE: 2-5 minutes vs. 15-20 minutes for entire monorepo
- Failed builds don't block other teams
3. Technology Flexibility 🛠️
While our current stack is Angular 18, polyrepo allows:
// Host: Angular 18
@Component({...})
export class HostApp {}
// Products MFE: Angular 18 + Latest Features
import { signal, computed } from '@angular/core';
// Cart MFE: Could use React (future flexibility)
// No impact on other MFEs as long as Module Federation contract is maintained
// Profile MFE: Could use Vue (if needed)
// Each MFE is truly independent
Real Example:
One team wanted to experiment with Angular's latest signal-based forms (experimental), while others preferred stable APIs. In polyrepo, this is trivial—each team controls their own package.json.
4. Granular Access Control 🔐
Enterprise Requirements:
# Financial services company scenario
products/ # Public team (20 developers)
cart/ # Public team (15 developers)
admin/ # Restricted (5 senior developers only)
analytics/ # Data team only (GDPR/compliance)
With separate repositories:
- ✅ GitHub permissions per repo
- ✅ Different security policies per MFE
- ✅ Audit trail per team
- ✅ Branch protection rules per team preference
5. Clearer Versioning & Dependencies 📦
Shared UI Kit Approach:
# Published as npm package
@hemantajax/mfe-uikit@1.2.3
// Products MFE (package.json)
{
"dependencies": {
"@hemantajax/mfe-uikit": "^1.2.0"
}
}
// Cart MFE (package.json) - Can use different version
{
"dependencies": {
"@hemantajax/mfe-uikit": "^1.1.0" // Still on older version
}
}
Advantages:
- ✅ Semantic versioning for shared code
- ✅ Teams upgrade at their own pace
- ✅ No "big bang" upgrades across all apps
- ✅ Backward compatibility testing becomes explicit
⚖️ Trade-offs: The Honest Truth
Advantages of Polyrepo ✅
| Aspect | Monorepo | Polyrepo |
|---|---|---|
| Team Autonomy | Low - Shared repo | High - Independent repos |
| Deployment Speed | Slow - Build everything | Fast - Build only what changed |
| Independent Deployments | Difficult | Easy |
| Technology Flexibility | Constrained | High |
| Access Control | Repository-level only | Per-MFE granular |
| CI/CD Pipeline | Single, complex | Multiple, simple |
| Version Management | Implicit via monorepo | Explicit via npm versions |
| Onboarding New Teams | One repo to clone | Multiple repos (more setup) |
| Team Scaling | Difficult beyond 20-30 devs | Easy to scale to 100+ devs |
Disadvantages of Polyrepo ❌
1. More Complex Setup
Monorepo:
git clone mfeworld
npm install
nx serve dashboard # Everything works
Polyrepo:
# Clone multiple repositories
git clone mfe-host
git clone mfe-uikit
# Each MFE team clones their own repo
git clone products
git clone cart
...
# Install dependencies in each
cd mfe-host && npm install
cd ../products && npm install
cd ../cart && npm install
Mitigation:
- Use workspace management tools (meta, git submodules)
- Provide setup scripts
- Docker Compose for local development
# docker-compose.yml
services:
host:
build: ./mfe-host
ports: ['4200:4200']
products:
build: ./products
ports: ['4201:4201']
cart:
build: ./cart
ports: ['4202:4202']
2. Shared Code Management
Monorepo:
// Direct imports, no publishing needed
import { HeaderComponent } from '@nxmfe/shared/layout';
Polyrepo:
// Must publish to npm/GitHub Packages
import { HeaderComponent } from '@hemantajax/mfe-uikit';
// Requires:
// 1. Build UI Kit
// 2. Publish to registry
// 3. Update version in consuming MFEs
// 4. npm install in each MFE
Mitigation:
- Automated publishing via GitHub Actions
- Semantic versioning
- Changelog automation
- Consider using Renovate Bot for dependency updates
# .github/workflows/publish-uikit.yml
name: Publish UI Kit
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: nx build uikit
- run: npm publish dist/libs/uikit
3. Cross-Cutting Changes
Scenario: Update authentication logic across all MFEs
Monorepo:
# Single PR affects all apps
nx run-many --target=test --all # Test everything
git commit -m "feat: update auth" # One commit
Polyrepo:
# 1. Update UI Kit
cd mfe-uikit
# Make changes to AuthService
git commit -m "feat: update auth"
npm version minor # 1.2.0 → 1.3.0
git push
# 2. Update each MFE (8 separate PRs!)
cd products
npm update @hemantajax/mfe-uikit@latest
git commit -m "chore: update uikit"
cd cart
npm update @hemantajax/mfe-uikit@latest
git commit -m "chore: update uikit"
# ... repeat for 6 more MFEs
Mitigation:
- Automation scripts for bulk updates
- Renovate Bot for automatic PR creation
- Feature flags for gradual rollout
- Backward compatible changes
4. Testing Integration
Monorepo:
# Easy to run integration tests
nx run-many --target=e2e --all
Polyrepo:
- Need separate integration test repository
- Must coordinate MFE versions
- More complex CI/CD orchestration
Solution:
# Separate integration-tests repository
integration-tests/
├── tests/
│ ├── user-journey.spec.ts # Test across MFEs
│ └── checkout-flow.spec.ts
└── playwright.config.ts
# Uses deployed versions
playwright.config.ts:
use: {
baseURL: 'https://hemantajax.github.io/mfe-host/'
}
5. Debugging Across Repositories
Monorepo:
- Single IDE workspace
- Easy to set breakpoints across apps
- Cmd+Click to jump between files
Polyrepo:
- Multiple IDE windows
- Need to use
npm linkfor local UI Kit development - Debugging shared code requires linking
Workflow:
# Develop UI Kit locally
cd mfe-uikit
npm link
# Link in consuming MFE
cd ../products
npm link @hemantajax/mfe-uikit
# Make changes, see updates in real-time
cd ../mfe-uikit
# Edit code → products automatically picks up changes
🏗️ Architecture Deep Dive
Module Federation Configuration
The magic that makes it all work:
Host Application (mfe-host):
// module-federation.config.prod.ts
export default {
name: 'shell',
remotes: [
['products', 'https://hemantajax.github.io/mfedemos/products/remoteEntry.mjs'],
['cart', 'https://hemantajax.github.io/mfedemos/cart/remoteEntry.mjs'],
['profile', 'https://hemantajax.github.io/mfedemos/profile/remoteEntry.mjs'],
['orders', 'https://hemantajax.github.io/mfedemos/orders/remoteEntry.mjs'],
['analytics', 'https://hemantajax.github.io/mfedemos/analytics/remoteEntry.mjs'],
['notifications', 'https://hemantajax.github.io/mfedemos/notifications/remoteEntry.mjs'],
['messages', 'https://hemantajax.github.io/mfedemos/messages/remoteEntry.mjs'],
['admin', 'https://hemantajax.github.io/mfedemos/admin/remoteEntry.mjs'],
],
};
Route Configuration:
// app.routes.ts
export const appRoutes: Route[] = [
{
path: 'products',
loadChildren: () => import('products/Routes').then((m) => m.remoteRoutes),
},
{
path: 'cart',
loadChildren: () => import('cart/Routes').then((m) => m.remoteRoutes),
},
// ... other routes
];
Shared UI Kit Strategy
Publishing Workflow:
-
Single npm Package (
@hemantajax/mfe-uikit)
- Contains all shared components, services, utilities
- Versioned independently
- Published to GitHub Packages (or npm)
- Internal Organization (in UI Kit repo)
mfe-uikit/
├── libs/
│ ├── layout/ # Header, Footer, Sidebar
│ ├── components/ # Buttons, Forms, Cards
│ ├── services/ # API, Auth, Storage
│ ├── utils/ # Helper functions
│ ├── pipes/ # Custom pipes
│ ├── directives/ # Custom directives
│ └── uikit/ # ⭐ Meta package (re-exports all)
- Usage in MFEs:
// Before (monorepo):
import { HeaderComponent } from '@nxmfe/shared/layout';
import { AuthService } from '@nxmfe/shared/services';
// After (polyrepo):
import { HeaderComponent, AuthService } from '@hemantajax/mfe-uikit';
Benefits:
- ✅ Single version to manage
- ✅ One
npm installcommand - ✅ Simpler imports
- ✅ Can still organize code internally
📊 Real-World Metrics
Before (Monorepo)
- Repository size: 500MB (with .git)
- Full build time: 18 minutes
- CI pipeline: 25 minutes
- Deployments: 2-3 times per week
- PR review time: 2-3 days (bottleneck)
- Teams blocked: Often
After (Polyrepo)
- Repository size: 50-80MB per repo
- MFE build time: 2-5 minutes
- CI pipeline per MFE: 5-8 minutes
- Deployments: 10+ times per day (per team)
- PR review time: Same day
- Teams blocked: Never
🚀 When to Choose Polyrepo?
Choose Polyrepo When:
✅ You have multiple independent teams (3+ teams)
- Each team owns specific features/domains
- Teams want different release schedules
- Teams are in different time zones
✅ You need independent deployments
- Can't afford monorepo build times
- Need to deploy hotfixes to single MFE
- Different apps have different SLAs
✅ Enterprise requirements
- Need granular access control
- Compliance/security boundaries
- Different teams have different tech stacks
✅ Scaling beyond 20-30 developers
- PR bottlenecks in shared repo
- Difficult to coordinate releases
- CI/CD pipeline too complex
Choose Monorepo When:
✅ Small team (< 20 developers)
- Everyone can review all PRs
- Shared ownership model works
- Coordinated releases are preferred
✅ Rapid prototyping
- Quick iteration across multiple apps
- Frequent refactoring of shared code
- Atomic commits across apps are valuable
✅ Simple deployment
- Can afford to build everything
- Single deployment schedule works
- Less infrastructure complexity
🛠️ Implementation Checklist
If you're migrating from monorepo to polyrepo:
Phase 1: Preparation
- [ ] Identify team boundaries
- [ ] Define shared code strategy
- [ ] Set up package registry (npm, GitHub Packages)
- [ ] Plan repository structure
- [ ] Document architecture decisions
Phase 2: Extract Shared Code
- [ ] Create UI Kit repository
- [ ] Migrate shared libraries
- [ ] Set up automated publishing
- [ ] Test package installation
- [ ] Document shared API
Phase 3: Split Micro-Frontends
- [ ] Create repo for each MFE
- [ ] Migrate code and dependencies
- [ ] Set up individual CI/CD
- [ ] Update Module Federation configs
- [ ] Test remote loading
Phase 4: Update Host
- [ ] Update remote URLs
- [ ] Update production configs
- [ ] Test integration
- [ ] Set up monitoring
- [ ] Document deployment process
Phase 5: Developer Experience
- [ ] Create setup scripts
- [ ] Write comprehensive docs
- [ ] Set up local development workflow
- [ ] Automate dependency updates
- [ ] Train teams
🔧 Developer Experience Tips
1. Local Development Setup
Use npm link for UI Kit development:
# Terminal 1: UI Kit (watch mode)
cd mfe-uikit
npm run build:watch
# Terminal 2: Link UI Kit
cd mfe-uikit
npm link
# Terminal 3: Products MFE
cd products
npm link @hemantajax/mfe-uikit
npm start
2. Automated Dependency Updates
Renovate Bot Configuration:
{
"extends": ["config:base"],
"packageRules": [
{
"matchPackagePatterns": ["@hemantajax/mfe-uikit"],
"groupName": "UI Kit",
"automerge": true,
"automergeType": "pr"
}
]
}
3. Integration Testing
Separate test repository:
// integration-tests/tests/checkout.spec.ts
import { test, expect } from '@playwright/test';
test('complete checkout flow', async ({ page }) => {
// Navigate to host
await page.goto('https://hemantajax.github.io/mfe-host/');
// Interact with products MFE
await page.goto('/products');
await page.click('[data-testid="add-to-cart"]');
// Interact with cart MFE
await page.goto('/cart');
await expect(page.locator('[data-testid="cart-item"]')).toBeVisible();
// Interact with orders MFE
await page.click('[data-testid="checkout"]');
await page.goto('/orders');
await expect(page.locator('[data-testid="order-success"]')).toBeVisible();
});
📚 Resources
Live Demo & Source Code
- Demo: https://hemantajax.github.io/mfe-host/
- Host Repo: https://github.com/hemantajax/mfe-host
- UI Kit Repo: https://github.com/hemantajax/mfe-uikit
Remote MFEs (Deployed)
- Products: https://hemantajax.github.io/mfedemos/products/
- Cart: https://hemantajax.github.io/mfedemos/cart/
- Profile: https://hemantajax.github.io/mfedemos/profile/
- Orders: https://hemantajax.github.io/mfedemos/orders/
- Analytics: https://hemantajax.github.io/mfedemos/analytics/
- Notifications: https://hemantajax.github.io/mfedemos/notifications/
- Messages: https://hemantajax.github.io/mfedemos/messages/
- Admin: https://hemantajax.github.io/mfedemos/admin/
Related Articles
🎯 Conclusion
The shift from monorepo to polyrepo wasn't just about technology—it was about empowering teams to work independently while maintaining a cohesive architecture.
Key Takeaways:
Polyrepo isn't inherently better than monorepo—it's a trade-off based on team size and organizational needs
Start with monorepo if you're a small team or prototyping—the simplicity is worth it
Migrate to polyrepo when team boundaries become clear and independent deployments become critical
Invest in tooling to mitigate polyrepo complexity (automation, scripts, docs)
Module Federation makes polyrepo micro-frontends feasible—without it, this architecture would be painful
What Would I Do Differently?
Looking back, I would:
Create the UI Kit earlier - Even in monorepo, a separate UI Kit would have helped with versioning
Automate more - Set up Renovate Bot and deployment automation from day one
Better documentation - Invest in comprehensive setup guides before splitting repos
Integration tests first - Have strong integration test coverage before splitting
💬 Discussion
What's your experience with monorepo vs. polyrepo? Have you tried micro-frontends in your organization?
I'd love to hear about:
- What challenges did you face?
- What would you do differently?
- Any tips for teams considering this architecture?
Drop a comment below! 👇
Follow me for more:
- Twitter: @hemantajax
- GitHub: @hemantajax
- LinkedIn: Hemant Singh
Tags: #microfrontends #angular #architecture #modulefederation #webpack #nx #monorepo #polyrepo #webdev #javascript #typescript #frontend
This article is based on real production experience migrating a medium-sized team from monorepo to polyrepo architecture. All code examples and demos are available in the linked repositories.

Top comments (0)