| name | microfrontend-patterns |
| description | Micro-frontend patterns — team topology decisions, Module Federation (webpack/Vite), integration strategies (iframe/web components/JS orchestration), shared state minimization, design system integration, and migration from monolith. |
Micro-Frontend Patterns
When to Activate
- More than 3 teams working on a single frontend application
- Teams need to deploy UI changes independently without coordinating releases
- Migrating a frontend monolith to a multi-team architecture
- Selecting an integration strategy (Module Federation vs Web Components vs iFrame)
- Designing shell application and remote app structure
- Handling shared state, routing, and design systems across teams
When to Use Micro-Frontends (and When Not To)
Use MFE When
- 3+ teams own distinct, bounded areas of the UI
- Teams deploy at different cadences and coupling causes coordination overhead
- Different UI regions have distinct technology requirements (e.g., React team + Vue team)
- Independent scalability per feature area is required
Do NOT Use MFE When
- Single team or 2 teams — coordination cost is negligible, complexity is not worth it
- Strong UI cohesion needed (animated transitions that cross team boundaries are painful)
- Early-stage product — MFE boundaries harden quickly and wrong splits are expensive to undo
- Performance is critical — additional network round-trips for remote entry files add latency
Decomposition Patterns
Vertical Decomposition (Recommended)
Each team owns a complete vertical slice: frontend UI + backend API + database.
Shell App (navigation, layout)
├── /checkout → Checkout Team (React app)
├── /catalog → Catalog Team (Vue app)
├── /account → Account Team (React app)
└── /support → Support Team (Angular app)
Horizontal Decomposition (Avoid Unless Necessary)
Teams own horizontal UI layers (header, footer, sidebar). Leads to tight coupling.
Module Federation (Webpack 5)
The standard for runtime integration. Shell loads Remote apps as JavaScript modules at runtime — no rebuild needed when a Remote deploys.
Shell Application (webpack.config.ts)
import { ModuleFederationPlugin } from "@module-federation/enhanced";
export default {
plugins: [
new ModuleFederationPlugin({
name: "shell",
remotes: {
checkout: `promise new Promise((resolve) => {
const url = window.__REMOTES__?.checkout ?? 'https://checkout.example.com/remoteEntry.js';
const script = document.createElement('script');
script.src = url;
script.onload = () => resolve(window.checkout);
document.head.appendChild(script);
})`,
},
shared: {
react: { singleton: true, requiredVersion: "^19.0.0" },
"react-dom": { singleton: true, requiredVersion: "^19.0.0" },
"react-router-dom": { singleton: true, requiredVersion: "^6.0.0" },
},
}),
],
};
Remote Application (webpack.config.ts)
import { ModuleFederationPlugin } from "@module-federation/enhanced";
export default {
output: {
publicPath: "auto",
},
plugins: [
new ModuleFederationPlugin({
name: "checkout",
filename: "remoteEntry.js",
exposes: {
"./CheckoutApp": "./src/CheckoutApp",
"./CheckoutButton": "./src/CheckoutButton",
},
shared: {
react: { singleton: true, requiredVersion: "^19.0.0" },
"react-dom": { singleton: true, requiredVersion: "^19.0.0" },
},
}),
],
};
Loading a Remote in the Shell
import React, { Suspense, lazy } from "react";
import ErrorBoundary from "./ErrorBoundary";
const CheckoutApp = lazy(() =>
import("checkout/CheckoutApp").catch(() => ({
default: () => <div>Checkout temporarily unavailable</div>,
}))
);
export default function App() {
return (
<Router>
<Routes>
<Route
path="/checkout/*"
element={
<ErrorBoundary fallback={<CheckoutFallback />}>
<Suspense fallback={<LoadingSkeleton />}>
<CheckoutApp />
</>
}
/>
);
}
TypeScript Support for Federation
new ModuleFederationPlugin({
dts: {
generateTypes: {
compilerOptions: {
outDir: "dist/@mf-types",
},
},
},
})
new ModuleFederationPlugin({
dts: {
consumeTypes: {
remoteTypesFolder: "@mf-types",
},
},
})
Module Federation with Vite
For Vite-based projects, use @originjs/vite-plugin-federation. Note: build-time federation rather than runtime — slightly less flexibility, better build performance.
import federation from "@originjs/vite-plugin-federation";
export default defineConfig({
plugins: [
react(),
federation({
name: "checkout",
filename: "remoteEntry.js",
exposes: {
"./CheckoutApp": "./src/CheckoutApp",
},
shared: ["react", "react-dom"],
}),
],
build: {
target: "esnext",
minify: false,
},
});
import federation from "@originjs/vite-plugin-federation";
export default defineConfig({
plugins: [
react(),
federation({
name: "shell",
remotes: {
checkout: "http://localhost:3001/assets/remoteEntry.js",
},
shared: ["react", "react-dom"],
}),
],
});
Alternative Integration Strategies
| Strategy | Isolation | DX | Performance | When to Use |
|---|
| Module Federation | Medium | Best | Best | Default — runtime integration |
| Build-time NPM packages | Low | Good | Best | Teams using same tech, frequent shared updates |
| iFrame | Maximum | Poor | Poor | Legacy apps, maximum security isolation |
| Web Components | High | Medium | Good | Framework-agnostic teams |
| Server-Side Composition | Medium | Medium | Best | SSR-heavy, CDN edge assembly |
iFrame Integration
function CheckoutIframe() {
return (
<iframe
src="https://checkout.example.com"
sandbox="allow-scripts allow-same-origin allow-forms"
allow="payment"
style={{ width: "100%", border: "none" }}
/>
);
}
Web Components
class CheckoutWidget extends HTMLElement {
connectedCallback() {
const root = this.attachShadow({ mode: "open" });
const app = document.createElement("div");
root.appendChild(app);
ReactDOM.createRoot(app).render(<CheckoutApp />);
}
disconnectedCallback() {
}
}
customElements.define("checkout-widget", CheckoutWidget);
<checkout-widget data-user-id="123"></checkout-widget>
Routing Architecture
The shell owns the top-level router. Remotes own sub-routes within their segment.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/catalog/*" element={<CatalogApp />} /> {}
<Route path="/checkout/*" element={<CheckoutApp />} /> {}
<Route path="/account/*" element={<AccountApp />} /> {}
</Routes>
<Routes>
<Route path="/" element={<CartView />} />
<Route path="/payment" element={<PaymentView />} />
<Route path="/confirmation" element={<ConfirmationView />} />
</>
Deep-Link Rule
Every route in every Remote must be directly navigable. Test: can a user bookmark /checkout/payment and return to it? If not, the routing is broken.
function CheckoutApp() {
return (
<BrowserRouter basename="/checkout">
{/* ... */}
</BrowserRouter>
);
}
Shared State & Communication
Guiding Principle: Minimize Shared State
If all teams share state, they're not truly independent. Ask: does this data really need to be shared?
Acceptable shared state:
- Auth session (user ID, auth token)
- UI theme / dark mode preference
- Feature flags (read-only)
NOT acceptable as shared state:
- Cart contents (checkout team owns this — other teams call the API)
- User profile details (account team owns this API)
- Product inventory (catalog team owns this)
Pattern 1: Custom Events (Loose Coupling)
window.dispatchEvent(
new CustomEvent("checkout:item-added", {
detail: { productId: "prod-123", quantity: 2 },
bubbles: true,
})
);
window.addEventListener("checkout:item-added", (e: CustomEvent) => {
updateCartBadge(e.detail.quantity);
});
Pattern 2: Shared Auth Store (Only for Global Session)
import { create } from "zustand";
interface AuthState {
userId: string | null;
token: string | null;
setAuth: (userId: string, token: string) => void;
clearAuth: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
userId: null,
token: null,
setAuth: (userId, token) => set({ userId, token }),
clearAuth: () => set({ userId: null, token: null }),
}));
Pattern 3: URL as State (Deep-Links, Shareable)
const params = new URLSearchParams(location.search);
const referralSource = params.get("ref");
Design System in MFE
Recommended: Shared Component Library as Remote
new ModuleFederationPlugin({
name: "designSystem",
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/components/Button",
"./Modal": "./src/components/Modal",
"./Theme": "./src/theme",
},
shared: { react: { singleton: true } },
});
CSS Isolation
.container { padding: 16px; }
const CartContainer = styled.div`
padding: 16px;
background: var(--color-surface); // CSS custom properties for theming
`;
For testing strategy (unit, integration, E2E), CI/CD independent deployments, error boundary resilience, and monolith-to-MFE migration, see skill microfrontend-patterns-advanced.
Related Skills
typescript-monorepo-patterns — managing multi-package repos (tooling complement to MFE)
frontend-patterns — component patterns for individual team apps
design-system — design system architecture (complements MFE design system strategy)
e2e-testing — Playwright E2E for full MFE composition testing