Micro frontends and monorepos form a powerful combination that can address many architectural challenges while providing developer productivity benefits. Here’s a comprehensive guide to effectively integrating these approaches:
Architectural Synergy
Why Monorepos for Micro Frontends?
- Unified Dependency Management
- Single source of truth for shared dependencies
- Eliminates version conflicts across micro frontends
// Example root package.json
{
"workspaces": [
"packages/shell",
"packages/mfe-header",
"packages/mfe-dashboard",
"packages/shared-libs"
]
}
- Cross-MFE Refactoring
- Atomic commits across multiple micro frontends
- Simplified dependency graph visualization
- Safe automated refactoring across boundaries
- Standardized Tooling
- Consistent build, test, and linting configurations
- Shared CI/CD pipelines
- Unified code generation
Implementation Patterns
1. Workspace Structure
Recommended Layout
monorepo/
├── packages/
│ ├── shell/ # Container application
│ ├── mfe-auth/ # Authentication MFE
│ ├── mfe-catalog/ # Product catalog MFE
│ ├── shared-ui/ # Common UI components
│ └── shared-utils/ # Shared utilities
├── tools/
│ ├── mfe-builder/ # Custom build tooling
│ └── deploy/ # Deployment scripts
└── package.json # Root configuration
2. Dependency Management
Yarn/NPM Workspaces
# Install all dependencies at root level
yarn install
# Add dependency to specific MFE
yarn workspace mfe-catalog add lodash
Selective Version Pinning
// packages/shell/package.json
{
"dependencies": {
"react": "18.2.0", // Pinned version
"shared-ui": "workspace:*" // Always use local version
}
}
3. Build Optimization
Incremental Builds
// Using Turborepo
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"mfe-catalog": {
"dependsOn": ["shared-ui"],
"outputs": ["dist/**"]
}
}
}
Shared Configurations
// Base webpack config in shared-tools
module.exports = {
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
}]
}
};
// MFE-specific extension
const { merge } = require('webpack-merge');
const baseConfig = require('shared-tools/webpack.base');
module.exports = merge(baseConfig, {
entry: './src/index.js',
// MFE-specific overrides
});
Development Experience
1. Local Development Setup
Linked Development
# Start shell and all MFEs in watch mode
yarn workspaces foreach -p run dev
Dynamic Module Replacement
// Shell dev server configuration
devServer: {
proxy: {
'/mfe-auth': 'http://localhost:3001',
'/mfe-catalog': 'http://localhost:3002'
}
}
2. Code Sharing Patterns
Internal Packages
// shared-ui/components/Button.jsx
export const Button = ({ children }) => (
<button className="shared-btn">{children}</button>
);
// mfe-catalog/package.json
{
"dependencies": {
"shared-ui": "workspace:*"
}
}
Versioned Contracts
// shared-contracts/src/types.ts
export interface Product {
id: string;
name: string;
price: number;
}
// mfe-catalog/src/components/ProductCard.tsx
import { Product } from 'shared-contracts';
Deployment Strategies
1. Unified CI/CD Pipeline
# .github/workflows/deploy.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: yarn install
- run: yarn build
- uses: actions/upload-artifact@v3
with:
path: |
packages/shell/dist
packages/mfe-auth/dist
packages/mfe-catalog/dist
2. Independent Deployment
# Deploy specific MFE only
yarn workspace mfe-catalog run deploy -- --env production
3. Versioned Artifacts
// Release script example
const version = require('./lerna.json').version;
const mfeName = process.argv[2];
fs.writeFileSync(
`packages/${mfeName}/dist/version.json`,
JSON.stringify({ version, build: Date.now() })
);
Advanced Patterns
1. Module Federation with Monorepo
// shell/webpack.config.js
new ModuleFederationPlugin({
remotes: {
mfe_auth: `mfe_auth@http://localhost:3001/remoteEntry.js`,
mfe_catalog: `mfe_catalog@http://localhost:3002/remoteEntry.js`
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
});
2. Cross-MFE State Management
// shared-state/src/store.ts
import { createStore } from 'redux';
import rootReducer from './reducers';
export const store = createStore(rootReducer);
// mfe-auth/src/App.tsx
import { store } from 'shared-state';
import { Provider } from 'react-redux';
3. Monorepo-aware Testing
{
"jest": {
"projects": [
"<rootDir>/packages/*/jest.config.js"
],
"collectCoverageFrom": [
"packages/**/src/**/*.{js,jsx,ts,tsx}",
"!**/node_modules/**"
]
}
}
Challenges and Solutions
Challenge | Solution |
---|---|
Scaling to many MFEs | Implement package boundaries and domain grouping |
Long CI times | Selective testing based on changed files |
Version drift | Enforce sync-check tooling |
IDE performance | Configure workspace-aware IDE settings |
Migration Path
- Phase 1: Convert to monorepo with existing packages
- Phase 2: Implement shared tooling and configurations
- Phase 3: Introduce Module Federation
- Phase 4: Optimize build and test pipelines
- Phase 5: Implement advanced code sharing patterns
Tooling Recommendations
- Package Managers: Yarn Workspaces, pnpm
- Build Systems: Turborepo, Nx, Lerna
- Module Federation: Webpack 5
- Testing: Jest with multi-project config
- Linting: ESLint with shared configs