In this article, we'll walk through how to implement a scalable Microfrontend architecture in Angular using Webpack Module Federation with Nx, with a focus on Dynamic Module Federation.
What is Microfrontend?
Microfrontend is an architectural approach where a large frontend application is broken down into smaller, independent, self-contained apps — similar to how microservices work on the backend.
Each smaller app (called a remote) represents a specific business ___domain or UI section — for example:
- A
login
page - A
products
page - A
cart
orprofile
section
These micro apps can:
- Be developed by different teams
- Be deployed independently
- Use different release cycles
- Be dynamically loaded at runtime by a central shell app
What is a Host App?
The host is the main application — the shell or container — that:
- Loads one or more remote apps at runtime
- Manages global routing, layout, or shared services (like authentication or navigation)
Think of it like a dashboard or entry point where all microfrontends come together to form a complete UI.
What is a Remote App
A remote is a microfrontend — a standalone Angular app that:
- Has its own routing and components
- Can be developed/tested independently
- Is exposed to the host using Webpack Module Federation
The host consumes the remote app’s UI and logic as if it were a part of its own application — but without tight coupling.
In This Project Example
Host App: dashboard
- Acts as the entry point for the user
- Loads other microfrontend apps dynamically
- Has shared layout and routing logic
- Reads the manifest file to dynamically load remote apps at runtime
Remote App: login
- Contains a simple login form and logic
- Can be developed and deployed independently
- Is exposed to the host using Module Federation
The host (dashboard
) loads the remote (login
) using Dynamic Module Federation.
When the user navigates to /login, the host dynamically loads the remote login app and renders its content as if it were a part of the host.
What is Nx
Nx is a powerful tool for building and managing monorepos — where you keep multiple frontend and backend apps, libraries, and shared code in a single workspace.
It helps you:
- Generate apps and libraries quickly
- Share code easily across projects
- Speed up builds with smart caching
- Scale large codebases efficiently
What are advantages of using Nx for creating microfrontends
- Simplified setup
Nx provides powerful generators (nx g @nx/angular:host, remote, etc.) that automate the boilerplate for setting up Webpack Module Federation, routing, and lazy loading — saving a lot of time.
- Monorepo Support
Nx is built for monorepos, allowing you to manage multiple microfrontend apps and shared libraries in one workspace. This helps with:
- Better code sharing
- Easier dependency tracking
Consistent tooling
Automatic Dependency Sharing
When you use shared libraries (like an auth service), Nx automatically configures Module Federation sharing so they’re not duplicated across remotes — no manual Webpack config needed.
- Faster Builds with Caching
Nx uses smart build and test caching — if nothing has changed in a remote, it skips rebuilding it. This makes development and CI/CD pipelines faster.
And many more advantages...
What is static and dynmaic module federation
When building Microfrontends with Angular and Nx, the host app (like dashboard) needs to know where the remote apps (like login) are located.
There are two ways to do this: Static and Dynamic.
Static Module Federation (Build-time URL)
In Static Federation:
The remote app (login) is hardcoded in the host (dashboard) during the build.
That means: if the remote URL changes (for example, from http://localhost:4201 to https://staging.example.com/login), you must rebuild the host app to update the URL.
In This Project:
When we'll first create the dashboard and login apps using Nx generators, the host will be configured statically:
remotes: ['login'] // Nx internally resolves to http://localhost:4201
This works fine locally — but when deploying to staging or production, it becomes a problem.
Dynamic Module Federation (Runtime URL)
Dynamic Federation solves this.
Instead of hardcoding the remote URL, the host app (dashboard) loads the remote (login) dynamically at runtime using a small JSON file like this:
// module-federation.manifest.json
{
"login": "https://staging.example.com/login"
}
Now, you don’t need to rebuild the host.
You just change the manifest file depending on the environment.
In This Project:
We updated the main.ts of the dashboard app like this:
fetch('/module-federation.manifest.json')
.then(res => res.json())
.then(setRemoteDefinitions)
.then(() => import('./bootstrap'));
For real-world use (like CI/CD, multiple environments), always go with Dynamic Federation.
For learning or small apps, Static is fine to start with.
Let's begin to create. We'll create following:
- A Host app (dashboard) – loads other apps
- A Remote app (login) – login form
- Shared logic (user-auth service)
- Then convert this setup to Dynamic Module Federation
Step-by-Step: Building Microfrontends with Nx
Create Nx Workspace
npx create-nx-workspace@latest ng-mf --preset=apps
cd ng-mf
npx nx add @nx/angular
Create the Host App (Static Setup First)
nx g @nx/angular:host apps/dashboard --prefix=ng-mf
Create the Remote App (Login)
nx g @nx/angular:remote apps/login --prefix=ng-mf --host=dashboard
After creating the apps, if you check module-federation.config.ts in both, you can see:
In the Host (dashboard)
const config: ModuleFederationConfig = {
name: 'dashboard',
remotes: ['login'],
};
remotes
: Lists the remote apps to load (e.g., login).
In the Remote (Login)
const config: ModuleFederationConfig = {
name: 'login',
exposes: {
'./Routes': 'apps/login/src/app/remote-entry/entry.routes.ts',
},
};
exposes
: Defines what the remote shares with the host — like routes or components.
Create Shared Library
nx g @nx/angular:lib libs/shared/data-access-user
nx g @nx/angular:service user --project=data-access-user
Create a User service using RxJS BehaviorSubject to track login state
Add Login Form in Login Remote
In apps/login/src/app/remote-entry/entry.ts, add a login form UI and inject User service.
Update Dashboard App
- Show login form if user is not authenticated.
- Show dashboard content if user is logged in.
Add route (in apps/dashboard/src/app/app.routes.ts):
{
path: 'login',
loadChildren: () => import('login/Routes').then(m => m.remoteRoutes),
}
This is where dashboard (host) loads login (remote).
Converting Static Federation to Dynamic
a. Create a manifest file
apps/dashboard/public/module-federation.manifest.json
{
"login": "http://localhost:4201"
}
b. Update main.ts in dashboard
fetch('/module-federation.manifest.json')
.then(res => res.json())
.then(definitions => setRemoteDefinitions(definitions))
.then(() => import('./bootstrap'));
c. Remove remotes: ['login'] from module-federation.config.ts
d. Update route loading
loadChildren: () =>
loadRemoteModule('login', './Routes').then((m) => m.remoteRoutes),
Serve the app
nx serve dashboard --devRemotes=login // To start host app and link it with login app (which is remote app)
nx serve login // To start login app separately
For the complete code implementation (including dashboard and login content), please refer my below github repository.
GitHub repository for this project
What's next?
- Try implementing your own host app
- Try adding a second remote app (e.g.
profile
,cart
, orsettings
) - Deploy remotes to different environments and update the manifest to test runtime switching
Thanks for reading!
Top comments (0)