Microfrontends: A Practical, Production‑Ready Guide.
Microfrontends allow teams to build and deploy features independently while still delivering a unified user experience. This article explains practical patterns, Webpack Module Federation examples, and best practices for production-ready engineering teams.
New to Microfrontends? Read our Beginner-Friendly Step-by-Step Guide to Microfrontends with Module Federation (React + Webpack).
🚀 What Are Microfrontends?
Microfrontends break a large frontend into smaller, autonomous applications. Each team owns a specific business domain (cart, profile, search, dashboard), builds its UI independently, and deploys without affecting others.
The goal: Faster releases, isolated deployments, smaller bundles, and a scalable frontend architecture.
🏗 Microservices vs Microfrontends
| Category | Microservices | Microfrontends |
|---|---|---|
| Layer | Backend | Frontend UI |
| Communication | REST / gRPC / GraphQL | DOM events / message bus / shared state |
| Deployment | Independent service deployments | Independent UI deployments |
| Ownership | Backend teams | Frontend feature teams |
✅ Advantages of Microfrontends
- Independent UI Deployment — Deploy UI modules without touching others.
- Technology Freedom — Different microfrontends can use React, Angular, Vue, etc.
- Faster Development — Teams work in parallel with fewer merge conflicts.
- Better Scalability — Scale only the heavy parts of the UI.
- Incremental Migration — Move from legacy tech to modern stacks gradually.
- Fault Isolation — Failures in one microfrontend don't take down the whole app.
- Smaller Codebases — Easier to maintain and reason about.
- Reusable UI Components via Module Federation.
🔥 Key Engineering Challenges & Solutions
1️⃣ Manage Shared Dependencies & Version Conflicts
Use Module Federation to share dependencies at runtime and declare libraries as singletons:
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
}
Best practices: central shared registry, monorepo tooling (Nx, Lerna), version pinning, and container-level dependency injection.
2️⃣ Maintain a Consistent User Experience
Use centralized design tokens, a shared component library (or Storybook), unified routing (host handles top-level navigation), and consistent error handling.
3️⃣ Handle Cross‑Component Communication
Options include:
- Custom DOM Events (window.dispatchEvent / window.addEventListener)
- Lightweight Pub/Sub (mitt, PubSubJS)
- Shared global state via a small exposed store (Zustand, Redux Toolkit)
- URL-based state (query params)
4️⃣ Optimize Bundle Sizes & Performance
- Code splitting (React.lazy / import())
- Shared dependencies via Module Federation
- Bundle analysis (webpack-bundle-analyzer)
- Progressive loading and prioritization
- Service workers for caching
🧩 Working Example — Host + Remote (Webpack Module Federation)
Remote App (Feature Microfrontend) — webpack.config.js
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, 'src', 'index'),
output: { publicPath: 'auto', clean: true },
devServer: { port: 3001 },
module: { rules: [ { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ } ] },
plugins: [ new ModuleFederationPlugin({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: { './ProfileWidget': './src/ProfileWidget' },
shared: { react: { singleton: true }, 'react-dom': { singleton: true } }
}) ],
resolve: { extensions: ['.js', '.jsx'] }
}
Remote Example Component
export default function ProfileWidget({ user }){
return <h3>Welcome, {user?.name}</h3>;
}
Host App (Shell) — webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
devServer: { port: 3000 },
plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }), new ModuleFederationPlugin({
name: 'host_app',
remotes: { remote_app: 'remote_app@http://localhost:3001/remoteEntry.js' },
shared: ['react', 'react-dom']
}) ],
resolve: { extensions: ['.js', '.jsx'] }
}
Host Usage Example
import React, { Suspense } from 'react';
import { createRoot } from 'react-dom/client';
const RemoteProfile = React.lazy(() => import('remote_app/ProfileWidget'));
function App(){
return (
<Suspense fallback={<div>Loading...</div>}>
<RemoteProfile user={{ name: 'Host User' }} />
</Suspense>
);
}
createRoot(document.getElementById('root')).render(<App />);
📦 Run Instructions
Start the remote app (port 3001) and the host app (port 3000), then open http://localhost:3000. The host will load the remote dynamically and render the component.
Want to see a working example? Check out our complete implementation on GitHub: WebPack5-Micro-Frontend
✅ Summary
Microfrontends offer a path to independent deployments, parallel team ownership, and incremental migration strategies. Use Module Federation, shared design tokens, and consistent communication patterns to avoid common pitfalls.
✅ Changes After Using Microservices + Microfrontends
Below is a clean, structured explanation you can use in your notes, interview answers, or blog.
1️⃣ Architecture Changes
Before (Monolithic)- One backend codebase
- One frontend application
- Tight coupling
- Single deployment pipeline
- ✔ Backend split into small independent services
- ✔ Frontend split into standalone UI components (Microfrontends)
- ✔ Each microfrontend talks to specific microservices
- ✔ Uses API Gateway or BFF pattern
2️⃣ Frontend Changes (Microfrontend)
Before- One big React/Angular app
- Shared state, same build, same deployment
-
Multiple independent apps → integrated using:
- Webpack Module Federation
- iframe
- Single SPA
-
Each app has its own:
- Build
- Deployment
- Repository
- Version independence (can use React + Vue + Angular together)
3️⃣ Backend Changes (Microservices)
Before- Single backend
- All modules in same codebase
-
Backend divided into services:
- Auth Service
- Product Service
- Order Service
- Cart Service
-
Each service has:
- Own database
- Own deployment
- Own scaling
4️⃣ Team Structure Changes
Before- Teams divided by layers (Backend team, Frontend team)
-
Teams divided by business features
- Example:
- Cart Team → Cart Microfrontend + Cart Microservice
- Auth Team → Auth Frontend + Auth Backend
This is called Vertical slicing.
5️⃣ Deployment Changes
Before- One CICD pipeline
- Single deployment → if something breaks, entire app goes down
- Each microfrontend deploys separately
- Each backend microservice deploys separately
- Faster rollbacks
- Faster release cycles
6️⃣ Performance Changes
Benefits- ✔ Faster page load with lazy loaded microfrontends
- ✔ Backend services scale independently
- ✔ Caching improves because services are isolated
- ❌ More network calls
- ❌ More DevOps complexity
7️⃣ Code Changes
Before Microfrontends- One package.json
- One Webpack config
- Shared state in one Redux store
-
Each microfrontend has its own:
- package.json
- webpack.config.js
- build scripts
- independent git repo
8️⃣ What is it called after combining both?
- ✔ End-to-End Microservices Architecture
- ✔ Full-Stack Microservices Architecture
- ✔ Distributed Frontend + Backend Architecture
- ✔ Micro-Apps Architecture