Micro Frontends in Angular and React: A Deep Technical Guide for Scalable Front-End Architecture
Module Federation, Custom Elements, and orchestrators like Single-SPA enable independently evolving, maintainable applications with a seamless user experience.
Join the DZone community and get the full member experience.
Join For FreeMicro-frontends allow large teams to build independent UI modules that ship autonomously. Angular and React both support micro-frontend architecture using Webpack Module Federation. Angular benefits from strong structure and RxJS-based shared services, while React provides lightweight, flexible federated components. A hybrid Angular-React MFE system typically follows a shell-and-remotes architecture, with shared libraries, version-safe dependencies, and independent deployments.
What Micro Frontends Are (and Why They Matter)
Micro frontends split a large UI into independently developed and deployed applications that compose together at runtime.
Problems They Solve
- Long build & deployment pipelines for monolithic front-end apps
- Cross-team dependency conflicts and tight coupling
- Risky framework upgrades
- Difficulty scaling engineering teams
- Slow feature delivery due to centralized release cycles
Benefits
- Independent deployments
- Technology freedom (Angular + React = no problem)
- Smaller, maintainable codebases
- Domain ownership
- Easier migrations
Macro Architecture Overview
┌───────────────────────────────────────┐
│ SHELL │
│ Global Nav, Routing, Auth, Layout │
└───────────────┬───────────────────────┘
│
┌─────────────────────────────────────────────┼────────────────────────────────────────┐
│ │ │
▼ ▼ ▼
Angular MFE React MFE React/Angular MFE
/orders /dashboard /profile
Module Federation Module Federation Custom Elements
Folder Structure for Enterprise-Scale MFEs
A poly-repo style (common in enterprises):
microfrontends/
│
├── shell/ # Shell app (Angular or React)
│ ├── src/
│ ├── webpack.config.js
│ └── package.json
│
├── mfe-angular-orders/
│ ├── src/app/orders/
│ ├── src/app/app.module.ts
│ ├── webpack.config.js
│ └── package.json
│
├── mfe-react-dashboard/
│ ├── src/Dashboard.jsx
│ ├── webpack.config.js
│ └── package.json
│
└── mfe-react-profile/
├── src/Profile.jsx
├── webpack.config.js
└── package.json
Monorepo with Nx
apps/
shell/
mfe-orders/
mfe-dashboard/
mfe-profile/
libs/
shared-ui/
auth/
utils/
Nx simplifies dependency graphs, CI/CD, caching, and workspace generators.
Angular Micro Frontend Example (Module Federation)
Angular MFE (Orders App)
webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const { share } = require("@angular-architects/module-federation/webpack");
module.exports = {
output: {
uniqueName: "orders",
},
plugins: [
new ModuleFederationPlugin({
name: "orders",
filename: "remoteEntry.js",
exposes: {
"./OrdersModule": "./src/app/orders/orders.module.ts",
},
shared: share({
"@angular/core": { singleton: true, strictVersion: true },
"@angular/common": { singleton: true },
"@angular/router": { singleton: true },
}),
})
],
};
Angular Shell Loads the Orders Module
shell/src/app/app-routing.module.ts
const routes: Routes = [
{
path: "orders",
loadChildren: () =>
loadRemoteModule({
type: "module",
remoteEntry: "http://localhost:4201/remoteEntry.js",
exposedModule: "./OrdersModule",
}).then(m => m.OrdersModule),
},
];
Angular Orders Component Example
orders.component.ts
@Component({
selector: 'app-orders',
template: `
<h2>Orders</h2>
<ul>
<li *ngFor="let order of orders">{{ order.id }} - {{ order.status }}</li>
</ul>
`
})
export class OrdersComponent {
orders = [
{ id: 101, status: 'Shipped' },
{ id: 102, status: 'Processing' },
];
}
React Micro Frontend Example (Module Federation)
React Dashboard MFE
mfe-react-dashboard/webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "dashboard",
filename: "remoteEntry.js",
exposes: {
"./Dashboard": "./src/Dashboard.jsx",
},
shared: ["react", "react-dom"],
}),
],
};
React Dashboard Component
export default function Dashboard() {
return (
<div>
<h2>Analytics Dashboard</h2>
<p>Live metrics will appear here...</p>
</div>
);
}
Angular Shell Loading React Components
Step 1: React Render Wrapper
shell/src/app/react-renderer.ts
import React from "react";
import ReactDOM from "react-dom";
export function mountReact(Component: any, elementId: string) {
ReactDOM.render(<Component />, document.getElementById(elementId));
}
Step 2: Wrapper Component in Angular
@Component({
selector: 'app-dashboard-wrapper',
template: `<div id="dashboard-root"></div>`
})
export class DashboardWrapper implements AfterViewInit {
async ngAfterViewInit() {
const Dashboard = (await import("dashboard/Dashboard")).default;
mountReact(Dashboard, "dashboard-root");
}
}
Step 3: Routing
{
path: 'dashboard',
component: DashboardWrapper
}
The result? Beautiful Angular + React harmony.
React Shell Consuming Angular MFE
Angular MFE can be delivered as a Custom Element.
Angular MFE Exports a Web Component
import { createCustomElement } from '@angular/elements';
import { OrdersComponent } from './orders.component';
const ordersElement = createCustomElement(OrdersComponent, { injector });
customElements.define('orders-widget', ordersElement);
React Shell Uses It Natively
function App() {
return (
<div>
<h2>User Profile</h2>
<orders-widget></orders-widget>
</div>
);
}
Communication Between Micro Frontends
Communication is challenging in MFEs because apps must stay decoupled.
Event Bus Pattern
Use browser events:
window.dispatchEvent(new CustomEvent("cart:updated", { detail: count }));
Listener:
window.addEventListener("cart:updated", (e) => console.log(e.detail));
Cross-MFE Shared Store (exposed via Module Federation)
React Exports:
exposes: { "./store": "./src/store.js" }
Angular Imports:
const store = await import("dashboard/store");
store.subscribe(value => ...);
URL-based Communication
The following are used by apps like Amazon to communicate state.
- Route parameters
- Query params
- URL fragments
Deployment Patterns
Independent Deployments (Best Practice)
Every MFE deploys separately.
https://app.company.com/
https://orders.company.com/
https://dashboard.company.com/
https://profile.company.com/
Shell dynamically loads remoteEntry URLs.
CI/CD Workflow Example
- Lint → Test → Build
- Upload artifacts to S3 or CDN
- Shell periodically fetches updated
remoteEntry.json - Cache-busting via content hashes
Best Practices (Critical for Enterprise)
Keep MFEs small
Each app should represent a business domain.
Do not share too many libraries
This creates coupling.
Prefer runtime federation over build-time federation
Gives true deployment independence.
Add a versioning strategy for remotes
Prevent breaking shells.
Use monorepo (Nx) if teams share code
Use poly-repo if independence is priority.
Common Anti-Patterns
❌ Using iFrames for everything
❌ Sharing global state directly
❌ Hard-coding remote URLs in the shell
❌ Putting multiple business domains in one MFE
❌ Rebuilding the shell for every MFE change
When to Use Micro Frontends (and When Not To)
Use MFEs if:
✔ Your team is large (50+ engineers)
✔ Multiple frameworks are involved
✔ Release cycles must be independent
✔ You need long-term migration flexibility
Avoid MFEs if:
✘ You have a small app (< 10 engineers)
✘ You don’t have strong DevOps support
✘ Performance is a bigger priority than autonomy
Conclusion
Micro frontends offer a robust, scalable architecture for modern front-end development—especially for enterprise teams using Angular, React, or both. Using Module Federation, Custom Elements, and orchestrators like Single-SPA, companies can build maintainable, future-proof applications that evolve independently while delivering a seamless experience.
While Angular provides strong module boundaries and stable typing, React offers flexibility and a lightweight component-based architecture.
Combined, they form a powerful cross-framework ecosystem that supports large-scale innovation.
Opinions expressed by DZone contributors are their own.
Comments