Saturday, January 31, 2026

Angular 21 Micro FrontEnd (MFE)

Angular 21 @angular-architects/native-federation Shell + Remotes Auth + Roles


Micro Frontends with Angular 21: A Step-by-Step Shell + Remotes Build (Native Federation)

Demo: https://angular21mfe.ihaveverygoodwebsite.com/login

Source code: https://github.com/skyairwater/angular21-mfe-app

This post walks through a working Angular 21 micro-frontend setup using @angular-architects/native-federation. You’ll build a Shell (host) and three Remotes (merchant, credit, wealth), wire routing, add login + role access, and share state via Angular signals.

The simple mental model
Shell owns: layout + top navigation + global routing + authentication + role checks.
Remotes own: domain UI (merchant/credit/wealth) and can have their own internal routes.
Native Federation enables: shell downloads remote UI at runtime, only when users navigate.

1) What we’re building

We will create one Angular workspace containing multiple Angular apps (a micro-repo style layout):

projects/
  shell/     (host)
  merchant/  (remote)
  credit/    (remote)
  wealth/    (remote)

The shell will route to /merchant, /credit, /wealth, and dynamically load each remote using runtime federation.

What this means:

  • All projects share:

    • Node modules

    • Angular version

    • Tooling

  • But at runtime:

    • Each app is built and served independently

    • Each app can be deployed independently

This is why it’s called micro-repo, not mono-repo.


2) Create the workspace and apps

Start with an Angular workspace and generate the apps.

# Create the workspace (shell app)
ng new mfe-workspace --routing --style=css

cd mfe-workspace

# Create remote apps inside /projects
ng generate application shell --routing --style=css
ng generate application merchant --routing --style=css
ng generate application credit --routing --style=css
ng generate application wealth --routing --style=css
Ports (matches your working project)
shell: 4200 • merchant: 4201 • credit: 4202 • wealth: 4203

3) Install Native Federation dependencies

Your project uses @angular-architects/native-federation plus a couple of runtime helpers.

npm install @angular-architects/native-federation @softarc/native-federation-node es-module-shims
Why this matters in Angular 21
Angular 21 uses modern “strict” builders. Native Federation avoids the older “custom webpack hook” approach and integrates in a way that works cleanly with modern Angular builds.

4) Federation config for each app (exact pattern)

Each remote exposes its root component at ./Component, pointing to its app root file: ./projects/<remote>/src/app/app.ts. This is exactly how your repo does it.

Remote example: projects/merchant/federation.config.js

const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');

module.exports = withNativeFederation({
  name: 'merchant',
  exposes: {
    './Component': './projects/merchant/src/app/app.ts',
  },
  shared: {
    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
  },
  skip: ['rxjs/ajax','rxjs/fetch','rxjs/testing','rxjs/webSocket'],
  features: { ignoreUnusedDeps: true }
});

Repeat the same for credit and wealth (same structure, just change the name and path).

Host: projects/shell/federation.config.js

const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');

module.exports = withNativeFederation({
  name: 'shell',
  shared: {
    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
  },
  skip: ['rxjs/ajax','rxjs/fetch','rxjs/testing','rxjs/webSocket'],
  features: { ignoreUnusedDeps: false }
});
What “shared singletons” means
We share Angular packages as singletons so we don’t load multiple Angular runtimes. That prevents routing/DI conflicts and keeps bundles smaller.

5) The federation manifest (shell side)

Your shell initializes federation using a manifest file. In your project it lives here: projects/shell/public/federation.manifest.json

{
  "merchant": "http://localhost:4201/remoteEntry.json",
  "credit":   "http://localhost:4202/remoteEntry.json",
  "wealth":   "http://localhost:4203/remoteEntry.json"
}

This tells the shell where to find each remote entry at runtime. In production, these URLs would typically point to a CDN path instead of localhost.


6) Bootstrapping with federation (main.ts + bootstrap.ts)

Shell: projects/shell/src/main.ts (exact)

import { initFederation } from '@angular-architects/native-federation';

initFederation('federation.manifest.json')
  .catch(err => console.error(err))
  .then(_ => import('./bootstrap'))
  .catch(err => console.error(err));

Remotes: projects/merchant/src/main.ts (exact)

import { initFederation } from '@angular-architects/native-federation';

initFederation()
  .catch(err => console.error(err))
  .then(_ => import('./bootstrap'))
  .catch(err => console.error(err));

All apps: src/bootstrap.ts (exact pattern)

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';

bootstrapApplication(App, appConfig)
  .catch((err) => console.error(err));
Why there are two files: main.ts and bootstrap.ts
main.ts initializes federation first. Only after federation is ready do we import bootstrap.ts to start Angular. That timing is important for runtime remote loading.

7) Shell routing: load remotes on navigation (exact)

Your shell routes are defined in projects/shell/src/app/app.routes.ts. This file does three things:

  • Redirects default route to a module
  • Provides a /login route
  • Loads each remote with loadRemoteModule(remoteName, exposedPath)
import { Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/native-federation';
import { authGuard } from './core/guards/auth.guard';
import { LoginComponent } from './features/login/login.component';

export const routes: Routes = [
  { path: '', redirectTo: 'merchant', pathMatch: 'full' },
  { path: 'login', component: LoginComponent },

  {
    path: 'merchant',
    loadComponent: () =>
      loadRemoteModule('merchant', './Component').then((m) => m.App),
    canActivate: [authGuard],
    data: { role: 'merchant' }
  },
  {
    path: 'credit',
    loadComponent: () =>
      loadRemoteModule('credit', './Component').then((m) => m.App),
    canActivate: [authGuard],
    data: { role: 'credit' }
  },
  {
    path: 'wealth',
    loadComponent: () =>
      loadRemoteModule('wealth', './Component').then((m) => m.App),
    canActivate: [authGuard],
    data: { role: 'wealth' }
  }
];
Two-level routing explained
The shell owns top-level routes (/merchant, /credit, /wealth). Once a remote is loaded, it can also define its own internal routes under that prefix (your remotes currently keep routes empty, which is fine for a starter).

8) Authentication + Roles (exact implementation)

Security model used here
Login and role state live in the shell. The shell blocks unauthorized navigation using a route guard. Remotes don’t implement login — they simply render their domain UI once loaded.

Auth service (Signals-based state): core/auth/auth.service.ts

Your project uses Angular signals for state:

import { Injectable, signal, computed } from '@angular/core';
import { Router } from '@angular/router';

export type Role = 'merchant' | 'credit' | 'wealth' | 'admin';

export interface User {
  username: string;
  role: Role;
  name: string;
}

@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly USERS: Record<string, User> = {
    'merchantuser': { username: 'merchantuser', role: 'merchant', name: 'Merchant User' },
    'credituser':   { username: 'credituser',   role: 'credit',   name: 'Credit User' },
    'wealthuser':   { username: 'wealthuser',   role: 'wealth',   name: 'Wealth User' },
    'admin':        { username: 'admin',        role: 'admin',    name: 'Administrator' }
  };

  private readonly PASSWORD = 'abcd123';

  private _currentUser = signal<User | null>(null);
  currentUser = this._currentUser.asReadonly();
  isLoggedIn = computed(() => !!this._currentUser());

  constructor(private router: Router) {}

  login(username: string, password: string): boolean {
    if (password !== this.PASSWORD) return false;
    const user = this.USERS[username];
    if (!user) return false;
    this._currentUser.set(user);
    this.redirectAfterLogin(user.role);
    return true;
  }

  logout(): void {
    this._currentUser.set(null);
    this.router.navigate(['/login']);
  }

  hasAccess(requiredRole: Role): boolean {
    const user = this._currentUser();
    if (!user) return false;
    if (user.role === 'admin') return true;
    return user.role === requiredRole;
  }

  private redirectAfterLogin(role: Role): void {
    this.router.navigate([role === 'admin' ? '/merchant' : '/' + role]);
  }
}

Route guard: core/guards/auth.guard.ts

import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from '../auth/auth.service';

export const authGuard: CanActivateFn = (route) => {
  const authService = inject(AuthService);
  const router = inject(Router);

  if (!authService.isLoggedIn()) {
    return router.createUrlTree(['/login']);
  }

  const requiredRole = route.data['role'];
  if (requiredRole && !authService.hasAccess(requiredRole)) {
    alert('You do not have access to this module.');
    return false;
  }

  return true;
};

Login component: features/login/login.component.ts

This is a simple username/password login UI calling the auth service.

// username examples: merchantuser / credituser / wealthuser / admin
// password: abcd123

9) Shell navigation (role-aware)

The shell shows navigation only when logged in, and it displays links based on role access. This is in projects/shell/src/app/app.html.

<nav *ngIf="authService.isLoggedIn()">
  <a *ngIf="authService.hasAccess('merchant')" routerLink="/merchant">Merchant</a>
  <a *ngIf="authService.hasAccess('credit')" routerLink="/credit">Credit</a>
  <a *ngIf="authService.hasAccess('wealth')" routerLink="/wealth">Wealth</a>
  ...
</nav>
State sharing (simple & effective)
The shell owns user state (signal). Any shell UI (navbar, logout, welcome message) reads directly from that signal. This is the simplest form of cross-module state: global state stays in the shell.

10) Remotes: what they expose and what they render

Each remote exposes its root component (App) from projects/<remote>/src/app/app.ts. Your remotes render simple UI blocks (dashed border + label) so it’s obvious they’re loaded remotely.

// projects/merchant/src/app/app.html (similar for credit/wealth)
<div style="border: 2px dashed red; padding: 20px;">
  <h1>Merchant App</h1>
  <p>This is the Merchant micro-frontend loaded remotely.</p>
</div>

11) angular.json and ports (how local dev works)

In your setup, each app has a federation build/serve target, and each app also has a “serve-original” dev server entry with a fixed port:

shell  -> 4200
merchant -> 4201
credit -> 4202
wealth -> 4203
Important note about modern Angular builders
Angular 21 validates build schemas strictly. Native Federation is designed to work cleanly within modern Angular tooling without relying on old “webpack transform hooks”.

12) Run it locally (exact workflow)

Open 4 terminals and run:

# Terminal 1
ng serve shell

# Terminal 2
ng serve merchant

# Terminal 3
ng serve credit

# Terminal 4
ng serve wealth

Then open: http://localhost:4200
You’ll be redirected to login. Use:

Users:
  merchantuser  (role: merchant)
  credituser    (role: credit)
  wealthuser    (role: wealth)
  admin         (role: admin)

Password:
  abcd123

After login, try navigating to different modules. Only allowed modules will show in the navbar and pass the route guard.


13) Next enhancements (easy, real-world improvements)

  • Add internal routes inside remotes (e.g., merchant/dashboard, merchant/reports)
  • Add a shared library for types/contracts (UserContext, Role)
  • Replace hardcoded login with a real backend token API
  • Add a shared “global notification” signal in shell that remotes can trigger via a small shared contract
  • Deploy: host remotes on CDN and update the shell manifest URLs
One sentence summary
This Angular 21 micro-frontend architecture works by initializing federation first, using a manifest to discover remotes, and loading remote components on route navigation—while the shell centrally manages authentication, roles, and shared state.

Roles of Each Application

1️⃣ Shell Application (Host)

This is the entry point.

Responsibilities:

  • Runs on localhost:4200

  • Owns:

    • Top navigation

    • Layout

    • Global routing

  • Decides WHEN and WHERE to load MFEs

Important:

The shell does NOT contain business features.

Think of it like:

“I don’t know how merchant works, I only know where to load it.”

 

2️⃣ Merchant / Credit / Wealth (Remotes)

These are independent Angular apps.

Responsibilities:

  • Own their own:

    • UI

    • Routing

    • State

  • Expose one or more entry components to the shell

Important:

They do NOT know about the shell.

They just say:

“If someone wants me, this is what I expose.”


🔌 How These Apps Talk (Conceptually)

They do NOT import each other at build time.

Instead:

  • Webpack Module Federation loads code at runtime

  • The shell:

    • Knows where the remote lives

    • Knows what the remote exposes

  • The remote:

    • Exposes a component/module

    • Doesn’t care who consumes it

This loose coupling is the entire point of MFE.


🚦 Runtime Flow (Very Important)

When you open:

http://localhost:4200/merchant

What happens behind the scenes:

  1. Browser loads shell

  2. Angular router matches /merchant

  3. Shell router says:

    “This route belongs to Merchant MFE”

  4. Webpack:

    • Downloads merchant’s remote bundle

    • Initializes it

  5. Merchant’s exposed component renders inside the shell

At no point is merchant bundled into shell.


🧠 Why Angular 21 Changes Everything

Angular 21 is:

  • Standalone-first

  • ESBuild-based

  • Strict builder validation

This means:

  • No NgModule declarations for root components

  • No legacy Webpack hacks

  • No random custom configs allowed

That’s why older tutorials caused:

  • Schema errors

  • Builder incompatibilities

  • Webpack hook failures

Your working setup respects Angular 21 rules — that’s why it runs clean.

 

🧭 STEP 2 — Routing in Micro Frontends (Angular 21)

1️⃣ Who owns routing?

✅ The Shell owns global routing

The shell decides:

  • /merchant

  • /wealth

  • /capital

  • /consumer

Think of shell routing as traffic control 🚦.

“When the URL changes, which application should be activated?”


❌ MFEs do NOT know global routes

Each MFE:

  • Knows only its internal routes

  • Does NOT care whether it is loaded at /merchant or /something-else

This is critical for independence.


2️⃣ Two Levels of Routing (Very Important)

🟦 Level 1 — Shell Routing

Example (conceptual):

/merchant → load Merchant MFE /wealth → load Wealth MFE

The shell:

  • Matches the route

  • Dynamically loads the remote

  • Mounts the exposed component

At this point, the shell’s job is done.


🟩 Level 2 — Remote (MFE) Routing

Once loaded:

/merchant/dashboard /merchant/reports

Now:

  • Merchant MFE router takes over

  • Shell is out of the picture

This is called nested routing.


3️⃣ How Shell Loads an MFE (Conceptually)

The shell route says:

“For this path, lazy-load a remote component.”

This is NOT:

  • import at build time

  • Static dependency

Instead:

  • The remote is fetched over the network

  • Loaded only when needed

That’s why MFEs improve:

  • Initial load time

  • Team independence

4️⃣Standalone Components Change Routing

In Angular 21:

  • Components are standalone

  • Routes load components, not modules

So instead of:

“Load MerchantModule”

It’s:

“Load MerchantAppComponent”

This:

  • Simplifies routing

  • Reduces boilerplate

  • Fits MFE architecture perfectly

6️⃣ Security & Role-Based Routing (Conceptual)

Yes — everything still works like a normal app:

  • Auth guards → live in shell

  • Role checks → done before loading MFE

  • Unauthorized users:

    • Never download the remote

    • Never see its UI

This is actually more secure than monoliths.

STEP 3 — Module Federation (Angular 21, MFE)

1️⃣ What is Module Federation?

Module Federation is a Webpack feature that allows:

  • One app (Shell) to load code from another app (Remote) at runtime

  • Without rebuilding or redeploying the shell

Think of it like:

“Importing JavaScript from another server dynamically.”

But safely, versioned, and controlled.


2️⃣ Roles in Module Federation

🟦 Shell (Host)

The shell:

  • Knows where the remotes live (URLs)

  • Decides when to load them

  • Does not bundle remote code at build time

Shell responsibility:

“I orchestrate, I don’t own features.”


🟩 Remote (MFE)

Each MFE:

  • Exposes one or more entry points

  • Builds independently

  • Is deployable without touching the shell

Remote responsibility:

“I expose capabilities, not my internals.”


3️⃣ Runtime Loading (The Key Concept)

Traditional Angular:

  • Everything bundled at build time

  • One deployment = everything

With Module Federation:

  • Shell loads a manifest

  • Downloads remote bundles only when route is activated

  • Executes remote code inside the shell

This is why:

  • /merchant loads merchant code

  • /wealth does NOT


4️⃣ Why Angular 21 Works So Well with MFEs

Angular 21:

  • Standalone components

  • No NgModule requirement

  • Cleaner dependency boundaries

Result:

  • Remotes expose a single root component

  • Shell consumes it directly

  • No complex module wiring

This removed 80% of historical MFE pain.


5️⃣ Shared Dependencies (Very Important)

Module Federation allows shared libraries:

  • Angular core

  • Router

  • RxJS

  • Design system libraries

Why this matters:

  • Prevents multiple Angular runtimes

  • Reduces bundle size

  • Avoids runtime conflicts

Interview phrase:

“Shared dependencies are singletons, ensuring only one Angular runtime is active.”


6️⃣ Version Safety (Why It Doesn’t Break)

Module Federation:

  • Negotiates versions at runtime

  • Uses compatible versions

  • Fails fast if incompatible

This avoids:

  • Random crashes

  • Silent failures

  • Dependency hell

Q: Is Module Federation Angular-specific?
👉 No. It’s a Webpack feature used by React, Vue, etc.

Q: How are remotes versioned?
👉 Independently deployed; shell loads by URL.

Q: What if a remote is down?
👉 Shell can show fallback UI or error boundary.

🧠 angular.json — What It Is & Why It Matters (Angular 21 + MFE)

1️⃣ What is angular.json?

angular.json is not configuration for Angular code — it is configuration for the Angular CLI.

Think of it as:

“How should the CLI build, serve, test, and package each application?”

Angular itself doesn’t read this file at runtime.
Only the CLI + builders do.


2️⃣ Why angular.json Became Strict in Angular 21

Modern Angular:

  • Uses schema-validated builders

  • Rejects unknown or legacy properties

  • Enforces clear boundaries

This is why you kept seeing errors like:

  • must have required property

  • must NOT have additional properties

This is intentional — Angular wants:

  • Predictable builds

  • Faster tooling

  • Fewer “magic” Webpack hacks


3️⃣ Workspace vs Projects (Big Picture)

In an MFE setup, angular.json describes multiple applications.

Conceptually:

  • One workspace

  • Many projects

    • Shell (host)

    • Multiple MFEs (remotes)

Each project is fully independent in how it:

  • Builds

  • Serves

  • Outputs bundles

This is how Angular supports multi-app architectures.


4️⃣ What a “Project” Represents

Each project entry defines:

  • Is this an application or library?

  • Where is its source code?

  • How do we build it?

  • How do we run it locally?

In MFE terms:

  • Shell = one project

  • Each MFE = one project

They do not share build pipelines.


5️⃣ Builders (This Is the Most Important Concept)

A builder answers one question:

“How should this task be executed?”

Examples of tasks:

  • build

  • serve

  • test

Angular 21 builders are:

  • Strict

  • Schema-validated

  • Optimized for esbuild

Why this mattered for you:

  • Some builders do not allow Webpack hooks

  • Some builders require specific properties

  • Mixing old + new builders causes failures

This explains 90% of your earlier pain.

7️⃣ How MFEs Use angular.json

In a micro frontend setup:

  • Shell:

    • Knows how to build itself

    • Knows how to serve itself

  • MFEs:

    • Build independently

    • Are served on different ports

    • Are deployed separately

angular.json ensures:

  • No accidental coupling

  • No shared build artifacts

  • Clean runtime federation

🔐 Authentication in Micro Frontend Architecture (Angular 21)

1️⃣ The Core Problem Auth Solves in MFE

In a monolithic SPA:

  • One login

  • One token

  • One router

  • One app owns everything

In micro frontends:

  • Multiple apps

  • Loaded dynamically

  • Possibly deployed separately

  • Potentially owned by different teams

So the real question is:

Who owns authentication and who trusts whom?


2️⃣ The Golden Rule (Memorize This)

Authentication is centralized. Authorization is distributed.

This one sentence explains everything.


3️⃣ Who Owns Authentication?

The Shell (Host Application)

The shell is responsible for:

  • Login screen

  • Credential validation

  • Token acquisition

  • Token storage

MFEs:

  • Never perform login

  • Never store credentials

  • Never refresh tokens

This avoids:

  • Duplicate login flows

  • Security holes

  • Token desync issues


4️⃣ What Does the Shell Actually Store?

Typically:

  • Access token (JWT)

  • User profile

  • Roles / claims

Stored in:

  • Memory (preferred)

  • Or session storage (acceptable)

  • Avoid localStorage in real banking apps

The shell becomes the security boundary.


5️⃣ How MFEs Get Auth Information

MFEs are not independent security domains.
They are guests inside the shell.

Common patterns:

  • Shared Auth Service

  • Shared State Store

  • Global Event / Signal

Conceptually:

“Shell authenticates once, MFEs consume identity.”

MFEs trust the shell.


6️⃣ Role-Based Access (Very Important)

Authorization happens at two levels:

🔹 UI Level

  • Which MFEs are visible?

  • Which routes are accessible?

  • Which buttons are enabled?

🔹 API Level

  • Backend validates token

  • Backend checks role claims

Even if a user hacks the UI:

  • Backend still enforces rules

This is defense in depth.


7️⃣ Routing + Auth (How They Work Together)

The shell:

  • Owns the router

  • Guards routes

  • Decides which MFE loads

Example (conceptually):

  • Admin → can load Admin MFE

  • Manager → can load Wealth MFE

  • Retail User → Retail MFE only

MFEs:

  • Assume access is already validated

  • Don’t re-check login

  • Only check feature-level permissions


8️⃣ Why MFEs Should NOT Authenticate Themselves

If each MFE did auth:

  • Multiple logins

  • Token conflicts

  • Logout chaos

  • Security gaps

Interview phrase:

“MFEs are runtime-integrated, not security-isolated.”

That sounds senior — because it is.


9️⃣ Token Propagation to Backend APIs

Flow:

  1. Shell authenticates

  2. Shell stores token

  3. HTTP interceptor attaches token

  4. API validates token

  5. API enforces role-based rules

MFEs:

  • Use the same HTTP client configuration

  • Do not know how the token was obtained

This keeps MFEs backend-agnostic.


🔟 Logout (Often Forgotten, Interview Gold)

Logout is:

  • Shell clears token

  • Shell resets shared state

  • Shell unloads MFEs

  • User is redirected to login

MFEs don’t handle logout logic.


1️⃣1️⃣ Why This Scales in Enterprises

This approach supports:

  • Independent teams

  • Separate deployments

  • Central security audits

  • Zero-trust backend APIs

Which is why:

  • Banks

  • FinTechs

  • Government apps

🔁 State Management in Micro Frontend Architecture (Angular 21)

1️⃣ Why State Is Tricky in MFEs

In a single SPA:

  • One global store

  • One runtime

  • One ownership model

In MFEs:

  • Multiple independently built apps

  • Loaded at runtime

  • Possibly from different deployments

So the key question becomes:

What state should be shared — and what should not?


2️⃣ The Core Principle (Memorize This)

Share as little state as possible.

Most MFE failures happen because teams:

  • Share too much

  • Couple lifecycles

  • Break isolation


3️⃣ Types of State in an MFE System

Think in layers:

🔹 Global / Cross-App State

Shared by all MFEs:

  • Auth info

  • User profile

  • Theme

  • Locale

  • Feature flags

Owned by: Shell


🔹 Domain State

Owned by a specific MFE:

  • Merchant data

  • Wealth portfolios

  • Capital market instruments

  • Consumer lending details

Owned by: Each MFE


🔹 Local UI State

Component-level:

  • Form inputs

  • Dropdown state

  • Modals

Never shared.


4️⃣ Who Owns Shared State?

Always:

The Shell owns shared state.

MFEs:

  • Read from it

  • React to it

  • Never mutate it directly (unless via contracts)

This prevents:

  • Hidden side effects

  • Debugging nightmares

  • Accidental coupling


5️⃣ How State Is Shared (Conceptually)

Common enterprise patterns:

✅ Shared Runtime Singleton

  • Auth service

  • User context service

✅ Global Store

  • Lightweight shared store

  • Not full Redux unless needed

✅ Signals / Observables

  • Push-based updates

  • MFEs subscribe, not poll

Key idea:

MFEs are consumers, not owners.


6️⃣ Why Angular Signals Matter (Angular 21 Context)

Signals:

  • Are synchronous

  • Are explicit

  • Have clear ownership

This makes them ideal for:

  • Auth state

  • User info

  • Feature toggles

They reduce:

  • Over-engineered global stores

  • Boilerplate

  • Async confusion


7️⃣ What NOT to Share Between MFEs

🚫 Do NOT share:

  • Component state

  • Form data

  • Business workflows

  • Navigation history

Each MFE must remain:

  • Replaceable

  • Deployable

  • Testable on its own


8️⃣ State + Routing Relationship

Flow:

  1. User logs in

  2. Shell updates auth state

  3. Router reacts

  4. Correct MFE loads

  5. MFE reads shared context

Routing decisions depend on:

  • Shared state

  • Not hardcoded rules


9️⃣ Common Anti-Patterns (Interview Gold)

❌ MFEs calling each other directly
❌ Shared mutable objects
❌ Global event chaos
❌ One giant shared Redux store

Interview phrase:

“Those patterns break isolation and defeat the purpose of MFEs.”


🔟 Real-World Example (Conceptual)

  • User logs in as Manager

  • Shell updates role state

  • Navigation updates

  • Wealth MFE becomes available

  • Merchant MFE hidden

  • APIs enforce same role rules

No MFE talks to another MFE.

💥 Why Micro Frontends Fail in Practice

Micro frontends don’t usually fail because of technology.
They fail because of people, process, and misuse.


1️⃣ MFEs Are Used When They’re Not Needed (Most Common)

What happens:

  • Small team

  • Single UI

  • Tight coupling

  • Still choose MFEs “for scalability”

Result:

  • More repos

  • More builds

  • Slower delivery

Truth:

If one team owns the UI, MFEs add complexity with no payoff.

Interview line:
“MFEs are an organizational scalability tool, not a technical one.”


2️⃣ Teams Share Too Much State

What happens:

  • Shared global stores

  • Shared domain objects

  • MFEs mutate each other’s data

Result:

  • Hidden dependencies

  • Race conditions

  • Impossible debugging

Why it fails:

Isolation is broken, but complexity remains.


3️⃣ Auth Is Implemented Multiple Times

What happens:

  • Each MFE handles login

  • Tokens stored in multiple places

  • Inconsistent logout behavior

Result:

  • Security gaps

  • Token desync

  • Broken sessions

Correct model:

One auth boundary. Always the shell.


4️⃣ Routing Is Not Centralized

What happens:

  • Each MFE defines its own top-level routes

  • Deep links break

  • Navigation becomes unpredictable

Result:

  • UX inconsistency

  • SEO issues

  • Hard-to-test flows

Rule:

The shell owns global routing. MFEs own internal routing only.


5️⃣ Build & Tooling Explosion

What happens:

  • Different Angular versions

  • Different build pipelines

  • Different lint rules

Result:

  • Integration hell

  • CI/CD fragility

  • “Works on my machine”

Why it fails:

MFEs increase coordination cost — tooling must be standardized.


6️⃣ Performance Gets Worse, Not Better

What happens:

  • Too many remote bundles

  • Duplicate dependencies

  • Poor lazy-loading strategy

Result:

  • Slower startup

  • Increased memory usage

Reality:

MFEs trade runtime simplicity for organizational flexibility.

If performance isn’t measured, MFEs quietly hurt UX.


7️⃣ Teams Don’t Have Clear Ownership

What happens:

  • Everyone touches everything

  • No clear domain boundaries

  • Shared responsibility without accountability

Result:

  • Slow decisions

  • Constant breaking changes

Rule:

Each MFE must have a clear owner and domain contract.


8️⃣ Versioning & Contract Drift

What happens:

  • Shell expects one API

  • MFE ships another

  • No backward compatibility

Result:

  • Runtime failures

  • Emergency rollbacks

Fix:

Treat MFEs like external consumers — versioned, documented contracts.


9️⃣ Over-Engineering State Management

What happens:

  • Redux, NgRx, event buses everywhere

  • Complex sync logic

Result:

  • Hard to reason about

  • Slower onboarding

  • Fragile flows

Truth:

Most MFEs only need auth + user context shared.


🔟 MFEs Hide Organizational Problems (The Big One)

What happens:

  • Poor communication

  • Unclear requirements

  • Weak product ownership

MFEs amplify these problems instead of fixing them.

Brutal truth:

MFEs don’t fix bad teams — they expose them.


1️⃣1️⃣ When MFEs Actually Succeed

MFEs work when:

  • Multiple teams

  • Clear domain boundaries

  • Strong platform team

  • Centralized auth & routing

  • Minimal shared state

  • Strict contracts

Without these, MFEs fail fast.

🚀 Real Production Deployment Patterns for Micro Frontends

1️⃣ The Core Production Question

In production, the key question is not:

“How do we build MFEs?”

It is:

“How do we deploy and update MFEs safely without breaking users?”

Everything else follows from this.


2️⃣ The Three Real-World Deployment Models

🔹 Model 1: Independent Deployment (Most Common & Recommended)

How it works:

  • Each MFE has:

    • Its own repo

    • Its own CI/CD pipeline

    • Its own deployment target

  • Shell loads MFEs at runtime via URLs

Characteristics:

  • Teams deploy independently

  • No coordinated releases

  • Rollbacks are localized

Used by:

  • Banks

  • Large SaaS products

  • Government portals

Interview line:

“Each MFE is deployed independently and discovered by the shell at runtime.”


🔹 Model 2: Version-Pinned Deployment (Safer but Slower)

How it works:

  • Shell explicitly pins MFE versions

  • MFEs publish versioned artifacts

  • Shell upgrade is deliberate

Characteristics:

  • More control

  • Fewer surprises

  • Slower rollout

Used by:

  • Regulated environments

  • Legacy-heavy systems


🔹 Model 3: Unified Release (Anti-Pattern for MFEs)

How it works:

  • All MFEs released together

  • Single pipeline

  • Tight coupling

Why it fails:

  • No independence

  • No real MFE benefit

Interview phrase:

“At that point, you might as well use a monolith.”


3️⃣ Where MFEs Are Actually Hosted

Common production setups:

✅ CDN-based hosting

  • Static assets

  • Fast global delivery

  • Cache control per MFE

✅ Object storage

  • S3 / Azure Blob / GCS

  • Versioned artifacts

✅ Reverse proxy

  • Nginx / Envoy

  • Routing based on path or subdomain

Shell never cares where MFEs live — only their URLs.


4️⃣ Runtime Discovery (Critical Concept)

Shell does not hardcode builds.

Instead, it:

  • Fetches remote entry points

  • Loads bundles dynamically

  • Integrates at runtime

This allows:

  • Hot updates

  • Canary releases

  • Gradual rollouts

Interview phrase:

“MFEs are integrated at runtime, not build time.”


5️⃣ Environment Configuration Strategy

Production-safe approach:

  • Shell knows:

    • Which environment it’s in

  • MFEs know:

    • Their own backend URLs

Avoid:

  • Hardcoded environments

  • Cross-MFE env coupling

This enables:

  • Blue/green deployments

  • Region-based routing


6️⃣ Rollback Strategy (Very Important)

If an MFE breaks:

  • Roll back only that MFE

  • Shell remains untouched

  • Other MFEs keep working

This is one of the biggest advantages of MFEs.

Interview line:

“MFEs allow localized rollback instead of full application rollback.”


7️⃣ Feature Flags + MFEs (Power Combo)

Production teams often combine:

  • MFEs + feature flags

This allows:

  • Gradual exposure

  • Role-based rollout

  • A/B testing

Shell decides:

  • Who sees which MFE

  • Based on user context


8️⃣ Failure Isolation (Underrated Benefit)

In production:

  • One MFE may fail to load

  • Shell still renders

  • Other MFEs continue working

Good shells:

  • Show fallback UI

  • Log telemetry

  • Don’t crash the entire app

This is resilience by design.


9️⃣ Observability & Monitoring

Each MFE:

  • Logs independently

  • Emits metrics

  • Has its own dashboards

Shell:

  • Correlates user sessions

  • Tracks load failures

MFEs are treated like:

“Frontend services”


🔟 Security in Production

Key points:

  • Tokens never embedded in builds

  • Auth handled at runtime

  • Backend APIs remain zero-trust

Production shells:

  • Validate token presence

  • Enforce route access

  • Never assume MFE correctness


Module Federation vs. Native Federation

Quick difference (1 line each)

✅ Native Federation (what you have)

  • Uses: remoteEntry.json + federation manifest

  • Loads: ESM-based remote modules

  • Designed for: modern Angular (17+) / Angular 21

✅ Module Federation (classic Webpack-based)

  • Uses: remoteEntry.js

  • Webpack runtime integration

  • Common in Angular 12–16 setups


Angular 21 Native Federation Ubuntu Nginx Cloudflare SSL (Flexible) Shell + Independent Remotes

Angular 21 Micro Frontend Deployment on Ubuntu with Nginx (Shell + Independent Remotes)

This document describes how to deploy an Angular 21 Micro Frontend (MFE) solution using Native Federation (@angular-architects/native-federation), Ubuntu Server, Nginx for static hosting, and Cloudflare SSL (Flexible mode). It follows a production-style micro-frontend deployment pattern: one Shell (Host) application and multiple Remote applications deployed independently.


1) Target Architecture

Subdomains Used
Component Type URL
Shell Host https://angular21mfe.ihaveverygoodwebsite.com
Merchant Services Remote https://merchantservice.ihaveverygoodwebsite.com
Wealth Remote https://wealth.ihaveverygoodwebsite.com
Credit Remote https://credit.ihaveverygoodwebsite.com

Why subdomains? Subdomains keep deployments clean and scalable: each Remote can be deployed independently, the Shell can dynamically load remotes from different locations, and the system avoids base-href/subpath complexities.


2) Build Output Structure (Angular 21)

This project generates production builds in the following format:

dist/<app-name>/browser/index.html
dist/<app-name>/browser/*.js

Examples:

dist/shell/browser/
dist/merchant/browser/
dist/credit/browser/
dist/wealth/browser/
Deployment rule
Only the browser/ folder is deployed to Nginx.

3) Server Deployment Folder Layout (Ubuntu)

The following directories are created on the Ubuntu server:

/var/www/angular21mfe/shell
/var/www/angular21mfe/merchant
/var/www/angular21mfe/credit
/var/www/angular21mfe/wealth

4) Copy Build Output to the Server

For each app, copy the contents of the browser/ folder into the target directory.

Shell

sudo mkdir -p /var/www/angular21mfe/shell
sudo rsync -av --delete dist/shell/browser/ /var/www/angular21mfe/shell/

Merchant

sudo mkdir -p /var/www/angular21mfe/merchant
sudo rsync -av --delete dist/merchant/browser/ /var/www/angular21mfe/merchant/

Credit

sudo mkdir -p /var/www/angular21mfe/credit
sudo rsync -av --delete dist/credit/browser/ /var/www/angular21mfe/credit/

Wealth

sudo mkdir -p /var/www/angular21mfe/wealth
sudo rsync -av --delete dist/wealth/browser/ /var/www/angular21mfe/wealth/

Apply permissions:

sudo chown -R www-data:www-data /var/www/angular21mfe
sudo chmod -R 755 /var/www/angular21mfe

5) Cloudflare DNS Setup (Required)

Create DNS entries for each subdomain and enable proxy mode (orange cloud). Cloudflare SSL mode used in this setup: Flexible.

  • angular21mfe.ihaveverygoodwebsite.com
  • merchantservice.ihaveverygoodwebsite.com
  • credit.ihaveverygoodwebsite.com
  • wealth.ihaveverygoodwebsite.com
Important note
Because the Shell is accessed via HTTPS, the federation manifest must reference remotes with HTTPS URLs to avoid browser mixed-content blocking.

6) Nginx Configuration Files

Each app uses a dedicated Nginx configuration file (as per your naming convention):

  • angular21mfe.ihaveverygoodwebsite.com.conf
  • merchantservice.ihaveverygoodwebsite.com.conf
  • wealth.ihaveverygoodwebsite.com.conf
  • credit.ihaveverygoodwebsite.com.conf

Each config supports:

  • Static hosting
  • SPA routing fallback (try_files ... /index.html)
  • Cross-subdomain federation loading using CORS headers (remotes only)
  • Non-caching of remoteEntry.json (remotes only)

7) Shell Nginx Config (Host App)

File: /etc/nginx/sites-available/angular21mfe.ihaveverygoodwebsite.com.conf

Purpose: Serve the Shell application from /var/www/angular21mfe/shell with SPA routing fallback.

server {
    listen 80;
    listen [::]:80;

    server_name angular21mfe.ihaveverygoodwebsite.com;

    root /var/www/angular21mfe/shell;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

8) Remote Nginx Config Template (Merchant / Credit / Wealth)

Remote requirements
  • Static hosting for the remote build
  • CORS headers so the Shell can fetch remoteEntry.json cross-domain
  • remoteEntry.json should not be cached (important for federation updates)

8.1 Merchant Remote Config

File: /etc/nginx/sites-available/merchantservice.ihaveverygoodwebsite.com.conf

server {
    listen 80;
    listen [::]:80;

    server_name merchantservice.ihaveverygoodwebsite.com;

    root /var/www/angular21mfe/merchant;
    index index.html;

    add_header Access-Control-Allow-Origin "https://angular21mfe.ihaveverygoodwebsite.com" always;
    add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" always;
    add_header Vary "Origin" always;

    location = /remoteEntry.json {
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

8.2 Wealth Remote Config

File: /etc/nginx/sites-available/wealth.ihaveverygoodwebsite.com.conf

server {
    listen 80;
    listen [::]:80;

    server_name wealth.ihaveverygoodwebsite.com;

    root /var/www/angular21mfe/wealth;
    index index.html;

    add_header Access-Control-Allow-Origin "https://angular21mfe.ihaveverygoodwebsite.com" always;
    add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" always;
    add_header Vary "Origin" always;

    location = /remoteEntry.json {
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

8.3 Credit Remote Config

File: /etc/nginx/sites-available/credit.ihaveverygoodwebsite.com.conf

server {
    listen 80;
    listen [::]:80;

    server_name credit.ihaveverygoodwebsite.com;

    root /var/www/angular21mfe/credit;
    index index.html;

    add_header Access-Control-Allow-Origin "https://angular21mfe.ihaveverygoodwebsite.com" always;
    add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" always;
    add_header Vary "Origin" always;

    location = /remoteEntry.json {
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

9) Enable Sites and Reload Nginx

Enable each site configuration and reload Nginx:

sudo ln -sf /etc/nginx/sites-available/angular21mfe.ihaveverygoodwebsite.com.conf /etc/nginx/sites-enabled/
sudo ln -sf /etc/nginx/sites-available/merchantservice.ihaveverygoodwebsite.com.conf /etc/nginx/sites-enabled/
sudo ln -sf /etc/nginx/sites-available/credit.ihaveverygoodwebsite.com.conf /etc/nginx/sites-enabled/
sudo ln -sf /etc/nginx/sites-available/wealth.ihaveverygoodwebsite.com.conf /etc/nginx/sites-enabled/

sudo nginx -t
sudo systemctl reload nginx

10) Update the Shell Federation Manifest (Production URLs)

This project uses Native Federation. The Shell reads remote URLs from: /var/www/angular21mfe/shell/federation.manifest.json

Update the manifest to point to the deployed remote subdomains:

{
  "merchant": "https://merchantservice.ihaveverygoodwebsite.com/remoteEntry.json",
  "credit": "https://credit.ihaveverygoodwebsite.com/remoteEntry.json",
  "wealth": "https://wealth.ihaveverygoodwebsite.com/remoteEntry.json"
}
Key outcome
The Shell dynamically loads each Remote using the URLs in this manifest, enabling independent deployments while still delivering one unified application experience.

11) Validation Checklist

Confirm Shell loads

Open: https://angular21mfe.ihaveverygoodwebsite.com

Confirm remoteEntry is reachable

Open these in a browser (each should return JSON):

  • https://merchantservice.ihaveverygoodwebsite.com/remoteEntry.json
  • https://credit.ihaveverygoodwebsite.com/remoteEntry.json
  • https://wealth.ihaveverygoodwebsite.com/remoteEntry.json

Confirm end-to-end navigation

From the Shell website, navigating to /merchant, /credit, and /wealth should dynamically load each remote module.


12) Key Benefits of This Deployment

  • Independent deployments: Each remote can be deployed and upgraded independently.
  • Central orchestration: The Shell provides a unified login, layout, and routing experience.
  • Enterprise-friendly: Clean separation by domain and scalable CI/CD design.
  • Cloudflare-compatible: HTTPS handled at edge, origin remains simple, and remotes load securely via HTTPS URLs.

Final Result
Shell (Host): https://angular21mfe.ihaveverygoodwebsite.com
Remotes:
https://merchantservice.ihaveverygoodwebsite.com
https://credit.ihaveverygoodwebsite.com
https://wealth.ihaveverygoodwebsite.com

Angular 21 Micro FrontEnd (MFE)

Angular 21 @angular-architects/native-federation Shell + Remotes Auth + Roles Micro Frontends with Angular 21: A St...