NX with Angular and Native Module Federation

A deep-dive into building a decentralized Microfrontend ecosystem using Nx, Angular +20 and native/remote/eject imports.

🌌 Angular CLI & Native Module Federation

image

Hi everyone, Micro-frontend is a massive subject. There is a lot I want to cover, but in this post, I want to keep things as simple as possible. I have created two new repositories to compare performance, scalability, security, clean code and build options between Nx and a standard Angular CLI setup (Only Native Module Federation). Let’s jump right into it!

Angular CLI with NX is a professional-grade monorepo demonstrating the future of Microfrontend (MFE) Architecture. It moves away from legacy Webpack-based Module Federation in favor of Native Federation, leveraging browser-standard Import Maps and high-performance Angular 21. Key benefit of Nx that stops you from rebuilding code that hasn't changed, making build times much faster. Also, a visual tool in Nx that helps you see and manage how different micro-frontends connect, keeping the project organized as it grows. Also, that helps manage complex MFE relationships, ensuring architectural integrity as the project scales.

🏗️ The Architecture

This project implements a Federated Runtime where the "Shell" (Host) and various "Planets" (currently Terra) are completely decoupled. To run the project locally, please ensure your Node.js version matches the one specified in the .nvmrc file, then run npm install to install all required packages.

Core Tech Stack

  • Framework: Angular 21 (Experimental/Cutting-edge)
  • Monorepo Tooling: Nx (Integrated Workspace)
  • MFE Strategy: Native Federation (Import Maps & ESM)
  • Node Version: v22.x (LTS)

🚀 Key Features

  • Zero-Overhead Sharing: Uses es-module-shims to allow the browser to handle dependency resolution via Import Maps, avoiding redundant vendor chunks.
  • Decoupled Deployments: Remotes are discovered at runtime via a federation.manifest.json. You can update a "Planet" without ever rebuilding the "Shell."
  • Task Orchestration: Replaced manual concurrently scripts with Nx Task Pipelines, allowing for parallel execution and computation caching.
  • Strict Namespacing: Implemented a unique route-mapping strategy (shellRoutes, terraRoutes) to prevent TypeScript declaration merging conflicts (TS2395).

🛠️ Technical Challenges & Solutions

1. Configuring Native Federation

Angular 21 supports Native Federation out of the box. We configure it in angular.json.

"architect": {
  "build": {
    "builder": "@angular/build:application",
    "options": {
      "polyfills": ["zone.js"],
      "esbuild": {
        "native": true
      },
      "federation": {
        "type": "dynamic",
        "filename": "remoteEntry.js",
        "exposes": {
          "./Module": "apps/terra/src/app/app.component.ts"
        },
        "shared": {
          "@angular/core": {
            "singleton": true,
            "strictVersion": true
          },
          "@angular/common": {
            "singleton": true,
            "strictVersion": true
          }
        }
      }
    }
  }
}

2. Dynamic Remote Discovery

Instead of hardcoding remote URLs, we use a federation.manifest.json that is discovered at runtime.

// apps/shell/src/federation.manifest.json
{
  "terra": {
    "url": "http://localhost:4201",
    "type": "dynamic"
  }
}

3. Loading Remotes

We use the dynamic import() syntax with the federation helper.

// apps/shell/src/app/app.component.ts
import { loadRemoteModule } from '@angular/core/mf';
 
async loadTerra() {
  const { TerraModule } = await loadRemoteModule({
    type: 'dynamic',
    remoteName: 'terra',
    exposedModule: './Module'
  });
}

🧩 Key Concepts

1. Import Maps

Import Maps allow us to control how the browser resolves module specifiers. In our case, they map remote names to their URLs.

// Generated by Nx
{
  "imports": {
    "@nx-galaxy/terra": "http://localhost:4201/remoteEntry.js"
  }
}

2. Shared Dependencies

To avoid duplicate dependencies, we mark libraries as shared. The browser will load the dependency once and share it among all applications.

3. Strict Versioning

Setting strictVersion: true ensures that if two applications require different versions of a shared library, the build will fail, preventing runtime errors.

4. Service Injection

The architecture utilizes a custom inject method to facilitate communication and share shared instances between the Shell and remote micro-frontends. By passing services via this injection layer, ensure that the Host and Remote Apps remain decoupled while still allowing the remote apps to access core functionality provided by the Shell. It is very usefull way to provide a data from shell to stateless microfrontend apps.

🚀 Benefits

1. Zero Runtime Overhead

Unlike Webpack Module Federation, we don't need a custom runtime to handle module loading. The browser's native ES Module loader handles everything so don't worry about the polyfills.

2. True Decoupling

Remotes can be deployed independently of the shell. As long as the federation.manifest.json is updated, the shell will automatically discover the new remote.

3. Performance

  • Import Maps eliminate the need for dynamic chunk loading.
  • Shared Dependencies reduce bundle sizes.

4. Running the Application

# Start the shell
npm run start:shell
 
# Start the remote
npm run start:terra

Angular CLI vs. with NX

In the evolving landscape of micro-frontends, the choice of tooling and architecture plays a pivotal role in scalability and developer experience. Today, we compare two approaches to building a federated application using Angular and Native Federation (a standard Angular CLI workspace) and nx-galaxy (an Nx-powered monorepo).

Project Overview

1. Standard Angular CLI

This project represents a comprehensive micro-frontend system. Angular CLI structure, managing all projects within a single angular.json file. Parallel execution is handled via the concurrently package.json with scripts. Managing multiple micro-frontends can be resource-intensive and difficult to orchestrate. Instead of relying on a messy concurrent execution script—such as concurrently ng serve shell ng serve app1 ... which consumes significant RAM and clutters the terminal, this architecture uses a more streamlined approach. Of course, with remote dev links and adding links to the federation.config.ts, it can save the day. Still, frontend is changing fast, so I think adapting to a new thing is more important than saving a day.

2. With NX

This project appears to be a migration or a streamlined version, currently containing 2 applications Shell and Terra.

It leverages Nx a powerful system, to manage the workspace. Configuration is decentralized, with each application having its own project.json simillar to webpack config.

Key Comparisons

FeatureStandard Angular CLIWith NX
Workspace Structureprojects/ folderapps/ and libs/ convention
ConfigurationMonolithic angular.json (Centralized)Modular project.json (Decentralized)
Task Executionnpm run serve:all (via concurrently)npx nx run-many ... (Native Nx capability)
Federation Technology@angular-architects/native-federation@angular-architects/native-federation
CachingStandard Angular build cacheNx Computation Caching (Local & Remote)
Dependency GraphImplicit / Mental ModelVisual nx graph

Important Notes

The "Standard" Approach

The project shows how far you can get with just the Angular CLI.

  • Pros: Familiar to any Angular developer. No extra tool learning curve.
  • Cons: angular.json becomes massive and hard to merge. Running tasks across 6 apps requires manual script maintenance (e.g., "serve:all": "concurrently 'ng serve shell' 'ng serve terra' ...").

The Nx Approach (nx-galaxy)

The nx-galaxy project demonstrates the benefits of modern tooling.

  • Pros: 3 main advantages on using Nx
    • Config Split: Each app owns its config, reducing merge conflicts.
    • Intelligent Execution: nx run-many schedules tasks efficiently.
    • Caching: If you built terra and didn't change it, Nx replays the output instantly.
  • Cons: Slight learning curve for Nx specific commands, especially on the build side.

Conclusion

While Angular CLI is a fully functional implementation with more micro-frontends included, Nx offers a superior architectural foundation.

For a production-grade micro-frontend system, my decide is migrating to Nx is a better choice. It solves the "monolithic configuration" problem and provides the task orchestration needed when your galaxy of micro-frontends grows from 6 to 60. Without Nx, the RAM costs these days will be high—especially since AI tools already consume so much memory. I tried to write this post as simplest way it possible and also In my next post, I’ll be diving into how to build microfrontend apps using this same approach!