Skip to content
Back to Knowledge Base

Migrate Angular Module Federation off the Nx wrappers

Migrate Angular Module Federation off the Nx wrappers

Migrate this Angular workspace off the Nx Module Federation APIs that Nx v24 removes. Follow every step at https://nx.dev/docs/kb/migrate-angular-module-federation.md in order. Default to Path A (keep the current bundler, use the official plugin). Take Path B (Native Federation) only when the user asks for it.

Rules 2 to 7 cover Path A. On Path B, follow that section of the page instead:

1. Before editing, run production builds and keep every `mf-stats.json` (`dist/apps/<app>/mf-stats.json` on webpack, `dist/apps/<app>/browser/mf-stats.json` on Rspack). Reproduce its `shared` array, `exposes`, and container name. If a generated Rspack app fails its initial `maximumError` budget, raise it. If webpack 5.111 or later fails with `Path variable [contenthash:20] not implemented`, pin webpack to 5.110 or earlier.

2. Replace `withModuleFederation` and `NxModuleFederationPlugin` from `@nx/module-federation/angular` with `ModuleFederationPlugin` from `@module-federation/enhanced/webpack` or `/rspack`, and drop `NxModuleFederationDevServerPlugin`. Keep `@nx/angular:webpack-browser` with `customWebpackConfig` on webpack, and `createConfig` from `@nx/angular-rspack` on Rspack.

3. Browser containers are ES modules: set `library: { type: 'module' }`, `experiments.outputModule: true` on webpack or `output.module: true` on Rspack, `filename: 'remoteEntry.mjs'` on webpack and `remoteEntry.js` on Rspack, `dts: false`, `output.publicPath: 'auto'`, `output.uniqueName`, and `optimization.runtimeChunk: false`. An Rspack host keeps its runtime chunk under the dev server. Write `remotes` as plain URLs with no `name@` prefix, one entry per remote even when the host stats list it once per exposed module.

4. On webpack, add `resolve.alias` entries for every workspace library referenced through `tsconfig` paths. Never alias a `<remote>/<exposed>` entry such as `remoteA/Routes`, `remoteNg/Module`, or a `federate-module` path. Rspack's `createConfig` reads the paths itself and needs no alias.

5. A host that already calls `registerRemotes` from `@module-federation/enhanced/runtime` is done, including `mf-manifest.json` entries with no `type`. For hosts on `@nx/angular/mf`, convert `setRemoteDefinitions` to `registerRemotes` with `type: 'module'` and `loadRemoteModule(name, './Routes')` to `loadRemote('<container-name>/Routes')`. Map keys and `loadRemote` prefixes are the normalized container name. Map values must be full `remoteEntry.mjs` URLs (`remoteEntry.js` on Rspack).

6. On webpack, replace the `serve` target with `@nx/angular:dev-server`. On Rspack keep the inferred targets. Keep each remote's `dependsOn: ['<host>:serve']`, set `headers: { 'Access-Control-Allow-Origin': '*' }` on every remote's serve target (on Rspack under `devServer.headers` in `rspack.config.ts`), and add a `serve-static` target per app using `@nx/web:file-server` with `staticFilePath`, `dependsOn: ['build']`, `spa: true`, `watch: false`, never `buildTarget`. Point Playwright `webServer.command`, or Cypress `webServerCommands` and `ciWebServerCommand`, at `nx run-many -t serve-static -p <host> <remotes>`.

7. Remove `@nx/module-federation` only after the new setup builds and runs. Move `@module-federation/enhanced` to `dependencies` (`pnpm add -P` on pnpm). Rebuild uncached, production last. On Rspack a development build needs `NGRS_CONFIG=development`, or it exits 0 with production output. After removing the package, rename any copy left in `node_modules` (pnpm keeps one under `.pnpm/` for optional peers) for one uncached build, then put it back.

8. Do not claim hot-update parity or shared-state parity from a green build. Remote edits reach the host after a reload, before and after this migration. On Rspack the first load after the dev server starts can need one reload. `Cannot find module '<id>'` while loading `./Routes` means a stale cache: run `nx reset`, delete `.angular/cache`, and rebuild. Report what you verified and what you did not.

9. On Path B, install the `@angular-architects/native-federation` release matching the workspace's Angular major and minor. A host calling `loadRemote('remoteA/Routes')` switches to `loadRemoteModule('remoteA', './Routes')`.

10. For apps with a server build (a `server` target or a `webpack.server.config.*` file), follow https://nx.dev/docs/kb/migrate-angular-module-federation-ssr after Path A. Path B does not cover server-side rendering. Stop and report if the user asks for Path B on one.

Reference: https://nx.dev/docs/kb/migrate-angular-module-federation.md

Migrate this Angular workspace off the Nx Module Federation APIs that Nx v24 removes. Follow every step at https://nx.dev/docs/kb/migrate-angular-module-federation.md in order. Default to Path A (keep the current bundler, use the official plugin). Take Path B (Native Federation) only when the user asks for it.

Rules 2 to 7 cover Path A. On Path B, follow that section of the page instead:

1. Before editing, run production builds and keep every `mf-stats.json` (`dist/apps/<app>/mf-stats.json` on webpack, `dist/apps/<app>/browser/mf-stats.json` on Rspack). Reproduce its `shared` array, `exposes`, and container name. If a generated Rspack app fails its initial `maximumError` budget, raise it. If webpack 5.111 or later fails with `Path variable [contenthash:20] not implemented`, pin webpack to 5.110 or earlier.

2. Replace `withModuleFederation` and `NxModuleFederationPlugin` from `@nx/module-federation/angular` with `ModuleFederationPlugin` from `@module-federation/enhanced/webpack` or `/rspack`, and drop `NxModuleFederationDevServerPlugin`. Keep `@nx/angular:webpack-browser` with `customWebpackConfig` on webpack, and `createConfig` from `@nx/angular-rspack` on Rspack.

3. Browser containers are ES modules: set `library: { type: 'module' }`, `experiments.outputModule: true` on webpack or `output.module: true` on Rspack, `filename: 'remoteEntry.mjs'` on webpack and `remoteEntry.js` on Rspack, `dts: false`, `output.publicPath: 'auto'`, `output.uniqueName`, and `optimization.runtimeChunk: false`. An Rspack host keeps its runtime chunk under the dev server. Write `remotes` as plain URLs with no `name@` prefix, one entry per remote even when the host stats list it once per exposed module.

4. On webpack, add `resolve.alias` entries for every workspace library referenced through `tsconfig` paths. Never alias a `<remote>/<exposed>` entry such as `remoteA/Routes`, `remoteNg/Module`, or a `federate-module` path. Rspack's `createConfig` reads the paths itself and needs no alias.

5. A host that already calls `registerRemotes` from `@module-federation/enhanced/runtime` is done, including `mf-manifest.json` entries with no `type`. For hosts on `@nx/angular/mf`, convert `setRemoteDefinitions` to `registerRemotes` with `type: 'module'` and `loadRemoteModule(name, './Routes')` to `loadRemote('<container-name>/Routes')`. Map keys and `loadRemote` prefixes are the normalized container name. Map values must be full `remoteEntry.mjs` URLs (`remoteEntry.js` on Rspack).

6. On webpack, replace the `serve` target with `@nx/angular:dev-server`. On Rspack keep the inferred targets. Keep each remote's `dependsOn: ['<host>:serve']`, set `headers: { 'Access-Control-Allow-Origin': '*' }` on every remote's serve target (on Rspack under `devServer.headers` in `rspack.config.ts`), and add a `serve-static` target per app using `@nx/web:file-server` with `staticFilePath`, `dependsOn: ['build']`, `spa: true`, `watch: false`, never `buildTarget`. Point Playwright `webServer.command`, or Cypress `webServerCommands` and `ciWebServerCommand`, at `nx run-many -t serve-static -p <host> <remotes>`.

7. Remove `@nx/module-federation` only after the new setup builds and runs. Move `@module-federation/enhanced` to `dependencies` (`pnpm add -P` on pnpm). Rebuild uncached, production last. On Rspack a development build needs `NGRS_CONFIG=development`, or it exits 0 with production output. After removing the package, rename any copy left in `node_modules` (pnpm keeps one under `.pnpm/` for optional peers) for one uncached build, then put it back.

8. Do not claim hot-update parity or shared-state parity from a green build. Remote edits reach the host after a reload, before and after this migration. On Rspack the first load after the dev server starts can need one reload. `Cannot find module '<id>'` while loading `./Routes` means a stale cache: run `nx reset`, delete `.angular/cache`, and rebuild. Report what you verified and what you did not.

9. On Path B, install the `@angular-architects/native-federation` release matching the workspace's Angular major and minor. A host calling `loadRemote('remoteA/Routes')` switches to `loadRemoteModule('remoteA', './Routes')`.

10. For apps with a server build (a `server` target or a `webpack.server.config.*` file), follow https://nx.dev/docs/kb/migrate-angular-module-federation-ssr after Path A. Path B does not cover server-side rendering. Stop and report if the user asks for Path B on one.

Reference: https://nx.dev/docs/kb/migrate-angular-module-federation.md

Nx v23 deprecates the Angular Module Federation generators, executors, runtime helpers, and the withModuleFederation and NxModuleFederationPlugin wrappers in @nx/module-federation/angular. These APIs wrap the official Module Federation plugin, which supports both webpack and Rspack. Nx v24 removes the wrappers, so migrate your apps to the official plugin directly.

Migrate before you upgrade to v24. The old packages still build, which gives you a baseline to compare against. Nx will also ship migrations for this later.

You have one decision to make before you start:

  • Path A keeps your bundler and swaps the wrapper for the official plugin. Your build target, routes, and deployment model stay the same. It is the conservative option and the smallest diff.
  • Path B moves to Native Federation and onto the esbuild-based @angular/build:application builder. It is the larger change, and the one more likely to track Angular's own direction.
Going away in v24Replacement
withModuleFederation from @nx/module-federation/angularModuleFederationPlugin from @module-federation/enhanced/webpack
NxModuleFederationPlugin from @nx/module-federation/angularModuleFederationPlugin from @module-federation/enhanced/rspack
setRemoteDefinitions, setRemoteDefinition from @nx/angular/mfregisterRemotes from @module-federation/enhanced/runtime
loadRemoteModule from @nx/angular/mfloadRemote from @module-federation/enhanced/runtime
@nx/angular:module-federation-dev-server@nx/angular:dev-server, plus the targets you start yourself
NxModuleFederationDevServerPluginNot supported
@nx/angular:module-federation-static-serverNot supported

The last two ran processes and built nothing. NxModuleFederationDevServerPlugin was the Rspack form of the dev-server executor, which started the host and every remote from one nx serve. module-federation-static-server served the remotes you weren't working on from one port. Step 6 replaces both with targets you start yourself.

A remote can render correctly while it and the host hold separate copies of a shared library. Record what works now:

  • Serve one host at a time with its remotes and visit the routes that load each remote.
  • Edit a file in the host and in a remote, and note how each change reaches the page.
  • Before you rebuild, copy each app's mf-stats.json to a folder such as baseline/. It's at dist/apps/<app>/mf-stats.json on webpack and dist/apps/<app>/browser/mf-stats.json on Rspack. Both paths reproduce its shared map, exposes, and container name.
  • Note each app's serve port for the remote URLs in Step 3.
Terminal window
grep -rE "@nx/module-federation|@nx/angular/mf|module-federation-dev-server|module-federation-static-server|module-federation-dev-ssr|withModuleFederationForSSR" --include="*.ts" --include="*.js" --include="*.json" --exclude-dir=node_modules --exclude-dir=.nx --exclude-dir=dist .

On Windows:

Terminal window
Get-ChildItem -Recurse -File -Include *.ts,*.js,*.json |
Where-Object FullName -notmatch 'node_modules|dist|\.nx' |
Select-String -Pattern '@nx/module-federation|@nx/angular/mf|module-federation-dev-server|module-federation-static-server|module-federation-dev-ssr|withModuleFederationForSSR'

A generated webpack host has withModuleFederation in webpack.config.ts and webpack.prod.config.ts, a module-federation.config.ts, a build target on @nx/angular:webpack-browser with customWebpackConfig, and a serve target on @nx/angular:module-federation-dev-server. A generated Rspack host has inferred targets and an rspack.config.ts that merges a webpack.config.ts holding NxModuleFederationPlugin and NxModuleFederationDevServerPlugin, plus a webpack.prod.config.ts that nothing loads. Remotes expose ./Routes (./Module for NgModule remotes) and any path federate-module added. Their serve target depends on the host's serve.

Sort your hosts into two groups by reading each src/main.ts. Step 5 only applies to the second group.

  • Static hosts have no federation call in main.ts. They reach remotes through an import('remoteA/Routes') in the routes file, resolved by a tsconfig.base.json path.
  • Runtime-loaded hosts fetch a manifest in main.ts. If that chain ends in init or registerRemotes from @module-federation/enhanced/runtime, the host needs no source change on Path A. Nx 23.2 generates --dynamic hosts this way. If it ends in setRemoteDefinitions from @nx/angular/mf, Step 5 converts it.

Server-side rendered apps (a server target or a webpack.server.config.ts) follow Path A, then migrate server-side rendered apps. Their server build fails in between.

Path A, the official plugin on webpack or Rspack

Section titled “Path A, the official plugin on webpack or Rspack”

Workspaces from the host generator already list @module-federation/enhanced, usually under devDependencies. Application code imports its runtime. Move it to dependencies, or install it if it's missing:

Terminal window
npm add --save-prod @module-federation/enhanced

Nx accepted a list of project names and resolved each one to a URL from the project graph. The official plugin needs the values written out.

The container name is normalized. Every character outside [a-zA-Z0-9_$], and a leading character that cannot start an identifier, becomes _. dyn-shell publishes as dyn_shell, and that is the form name takes.

The import alias stays the project name, and so do the remoteA/Routes entries in tsconfig.base.json that a static host's import() resolves through:

// a project named dyn-shell that consumes a project named remote-a
module.exports = {
name: 'dyn_shell', // container, normalized
remotes: {
'remote-a': 'http://localhost:4201/remoteEntry.mjs', // key is the project name
},
};

Every app needs its own map, copied from that app's baseline mf-stats.json. Nx shared every npm package the app depends on in the graph, plus each package's secondary entry points, so expect entries such as @angular/common/http and @angular/core/primitives/signals that your source never imports.

Take singleton, strictVersion, requiredVersion, and eager from each entry:

// dist/apps/shell/mf-stats.json, one entry of the shared array
{
"name": "@angular/core",
"version": "22.1.7",
"singleton": true,
"requiredVersion": "~22.1.0",
"strictVersion": true,
"eager": false,
}
apps/shell/module-federation.config.js
'@angular/core': { singleton: true, strictVersion: true, requiredVersion: '~22.1.0' },

eager: false is the plugin default and can be left out, but Rspack stats mark most @angular/* entries eager: true and those need copying.

Drop the version from npm entries. Keep it on source-only workspace libraries, which have no package.json for the plugin to read. The stats show them as version: '0.0.0' with requiredVersion: '^0.0.0' and no strictVersion.

Each remote gets a full URL on its serve port. The host's baseline mf-stats.json has these under remotes[].federationContainerName, one row per exposed module the host uses. Write one entry per remote. Angular containers are ES modules, and their URLs take no name@ prefix.

apps/shell/module-federation.config.ts
import type { ModuleFederationConfig } from '@nx/module-federation';
const config: ModuleFederationConfig = {
name: 'shell',
remotes: ['remoteA', 'remoteB'],
};
export default config;

Every app's config carries name, filename, library, and dts, and hosts add remotes. Remotes keep their existing exposes map, whose workspace-root paths resolve once Step 4 adds the workspace root to resolve.modules.

Production remote URLs live in webpack.prod.config.ts as ['remoteA', 'http://remote-a.example.com/'] tuples. On webpack the production configuration loads that file through customWebpackConfig. Rspack never loads it. Fold those URLs into the one config, adding the /remoteEntry.mjs that Nx appended for you. If the file only has the generator's commented example, you have one URL set for every configuration.

Key the URLs on the Nx task configuration, because the Angular builders don't set NODE_ENV.

apps/shell/module-federation.config.js
const isProd = process.env.NX_TASK_TARGET_CONFIGURATION === 'production';
module.exports = {
// ... name, filename, library, dts, shared
remotes: {
remoteA: isProd
? 'https://remote-a.example.com/remoteEntry.mjs'
: 'http://localhost:4201/remoteEntry.mjs',
},
};

Step 4: replace the wrapper in the bundler config

Section titled “Step 4: replace the wrapper in the bundler config”

@nx/angular:webpack-browser passes the Angular build's webpack config to the function your customWebpackConfig exports. That function was withModuleFederation. Replace it with one that adds the plugin and the settings the wrapper applied.

Before:

apps/shell/webpack.config.ts
import { withModuleFederation } from '@nx/module-federation/angular';
import config from './module-federation.config';
export default withModuleFederation(config, { dts: false });

After:

apps/shell/webpack.config.js
const {
ModuleFederationPlugin,
} = require('@module-federation/enhanced/webpack');
const { join } = require('node:path');
const mf = require('./module-federation.config.js');
module.exports = (config) => ({
...config,
output: { ...config.output, uniqueName: mf.name, publicPath: 'auto' },
experiments: { ...(config.experiments ?? {}), outputModule: true },
resolve: {
...config.resolve,
modules: [
...(config.resolve?.modules ?? ['node_modules']),
join(__dirname, '../..'),
],
alias: {
...(config.resolve?.alias ?? {}),
'@myorg/state': join(__dirname, '../../libs/state/src/index.ts'),
},
},
optimization: { ...config.optimization, runtimeChunk: false },
plugins: [...(config.plugins ?? []), new ModuleFederationPlugin(mf)],
});

Point customWebpackConfig.path at this file in project.json, delete the production configuration's override of it, and keep @nx/angular:webpack-browser as the executor.

Add the resolve.alias entries in every federated app, hosts included. The federation plugin resolves exposed modules outside the Angular compilation, which is the only place tsconfig paths apply. Alias workspace libraries only. Aliasing a <remote>/<exposed> path such as remoteA/Routes, remoteNg/Module, or a federate-module path bundles that remote into the host.

This step converts the runtime-loaded hosts from Step 1 that call setRemoteDefinitions from @nx/angular/mf. Static hosts and hosts already on registerRemotes skip it.

The host keeps its manifest and keeps ModuleFederationPlugin in its build with remotes: {}, since registerRemotes and loadRemote do nothing without the runtime it creates.

apps/dyn-shell/src/main.ts
import { setRemoteDefinitions } from '@nx/angular/mf';
fetch('/module-federation.manifest.json')
.then((res) => res.json())
.then(setRemoteDefinitions)
.then(() => import('./bootstrap'));
apps/dyn-shell/src/app/app.routes.ts
import { loadRemoteModule } from '@nx/angular/mf';
export const appRoutes: Route[] = [
{
path: 'remoteA',
loadChildren: () =>
loadRemoteModule('remoteA', './Routes').then((m) => m.remoteRoutes),
},
];

Map values must be full URLs. Append /remoteEntry.mjs (/remoteEntry.js on Rspack) to any bare origin, which setRemoteDefinitions used to complete for you. A remote's mf-manifest.json URL works too. Register remoteEntry.mjs values with type: 'module', while manifest values carry their own type.

Map keys and loadRemote prefixes use the normalized container name. A project named remote-a is remote_a in the manifest and loadRemote('remote_a/Routes') at the call site.

@nx/angular:module-federation-dev-server built and served the remotes along with the host, and --devRemotes picked which ones ran live. The official plugin only handles the build, so you start the processes yourself.

On webpack, switch the host's serve executor to @nx/angular:dev-server. Keep port, publicHost, and the per-configuration buildTarget options. Remove devRemotes, skipRemotes, static, isInitialHost, and pathToManifestFile, which the dev server rejects. On Rspack, keep the inferred build and serve targets.

Every remote needs headers: { 'Access-Control-Allow-Origin': '*' }, on the serve target on webpack or under devServer.headers in rspack.config.ts on Rspack. The host loads remoteEntry.mjs cross-origin, and the Angular dev server sends no CORS header by default.

Keep each remote's dependsOn: ['shell:serve'] and start everything you want live from one run-many, naming each app:

Terminal window
nx run-many -t serve -p shell remoteA remoteB

Replace every app's generated serve-static target, hosts included, with one that serves the build output directly:

apps/remoteB/project.json
{
"targets": {
"serve-static": {
"executor": "@nx/web:file-server",
"continuous": true,
"dependsOn": ["build"],
"options": {
"staticFilePath": "dist/apps/remoteB", // dist/apps/remoteB/browser on Rspack
"port": 4202,
"spa": true,
"watch": false,
},
},
},
}

Replace the whole target, including configurations and defaultConfiguration, and leave buildTarget unset. With it set, the file server rebuilds the app on top of the dependsOn build, and that nested build fails inside e2e runs. Keep spa: true so a refresh on a deep route doesn't 404.

Each remote's serve-static port must match the port in the host's remotes URL.

Generated Playwright projects start the host with nx run shell:serve, which no longer starts the remotes. Point webServer.command at nx run-many -t serve-static -p shell remoteA remoteB. A remote's own e2e project needs only -p remoteA, with webServer.url and baseURL on its serve-static port.

Cypress projects set these commands under nxE2EPreset in cypress.config.ts:

apps/shell-e2e/cypress.config.ts
nxE2EPreset(__filename, {
cypressDir: 'src',
webServerCommands: {
default: 'nx run-many -t serve-static -p shell remoteA remoteB',
production: 'nx run-many -t serve-static -p shell remoteA remoteB',
},
ciWebServerCommand: 'nx run-many -t serve-static -p shell remoteA remoteB',
});

In nx.json, drop the { "env": "NX_MF_DEV_REMOTES" } input from the @nx/angular:webpack-browser target default and keep the rest of the entry.

Check the result in a browser against your baseline:

  • Each remote route renders in the host.
  • A shared mutable value written in the host reads back in each remote after a client-side route change.
  • Editing a remote's component shows in the host after a reload, as in the baseline.
  • The e2e suite passes on the replacement web server.

Then check the builds:

  • Production and development both build with --skipNxCache after you delete dist/apps/<app> and .angular/cache. Build production last because both configurations write the same mf-stats.json. On Rspack, build development as NGRS_CONFIG=development nx build <app> --configuration=development.
  • Each app's production mf-stats.json lists the same container name, remotes, exposes, and shared entries as the baseline. Compare only those fields, since asset hashes and usedIn text change.

Once the new setup builds and runs, delete module-federation.config.ts, webpack.prod.config.ts, and on webpack webpack.config.ts. Rspack keeps webpack.config.ts because rspack.config.ts imports it.

Then remove the package and rebuild uncached:

Terminal window
npm remove @nx/module-federation

Native Federation does the same runtime composition with ES modules and import maps, on the esbuild @angular/build:application builder. Expect to rewrite the federation layer, starting from an init generator that expects an Angular CLI app.

Install the release matching your Angular major and minor. On Angular 22.1:

Terminal window
npm add @angular-architects/native-federation@~22.1.0

It belongs in dependencies.

Migrate remotes together with every host that loads them. A Module Federation host can't load a Native Federation remote. If a host can't move yet, keep its remotes on Path A until it can.

The generator refuses an app that already has a bootstrap.ts. Fold bootstrap.ts back into main.ts, delete it, and run the generator per app. On a runtime-loaded host, also drop the fetch(...) chain that ends in setRemoteDefinitions or registerRemotes, keeping only the bootstrap body. The dynamic-host type writes and reads its own public/federation.manifest.json. Delete the old module-federation.manifest.json.

Terminal window
nx g @angular-architects/native-federation:init --project=shell --port=4200 --type=host
nx g @angular-architects/native-federation:init --project=remoteA --port=4201 --type=remote

Use --type=host for a static host and --type=dynamic-host for one that reads a manifest.

The generator creates federation.config.mjs, tsconfig.federation.json, a new bootstrap.ts and main.ts, moves the app onto an esbuild target running @angular/build:application, wraps build and serve in @angular-architects/native-federation:build, and adds es-module-shims to the polyfills. It also adds es-module-shims under dependencies and @softarc/native-federation-orchestrator under devDependencies, and runs an install.

The host's remote map comes from every other application's serve port, or serve-original once that app has moved. Run the generator in any order. Apps with no port land at 4200, and Step 3 prunes them from the map. The wrapped serve gets port: 0 and falls through to the port on serve-original.

Delete dist/apps/<app> before the first build. The new output goes under browser/, next to the old webpack files. The first build also rewrites the files list in tsconfig.federation.json. Commit after the first build that follows Step 3.

On an Nx Module Federation app, the generator leaves this cleanup:

  • Delete module-federation.config.ts, webpack.config.ts, and webpack.prod.config.ts, and remove the customWebpackConfig it copied onto the esbuild target and its production configuration.
  • Replace the placeholder './Component' entry in each remote's exposes with the route file, as a path from the workspace root: exposes: { './Routes': './apps/remoteA/src/app/remote-entry/entry.routes.ts' }.
  • Share workspace libraries reached through tsconfig paths with sharedMappings: ['@myorg/state'] in every app's federation.config.mjs. shareAll only covers package.json dependencies.
  • Prune the host's remote map, which lists every application with build and serve targets under a camel-cased key, other hosts included. With --type=host the map is the first argument of initFederation in main.ts, and with --type=dynamic-host it is public/federation.manifest.json. Keep the hostRemoteEntry option the generator writes next to it.
  • On every app's serve-original target, swap the executor to @angular/build:dev-server and drop publicHost, which that builder does not accept. Keep a remote's headers.
  • Point extract-i18n at <app>:esbuild on @angular/build:extract-i18n.
  • Mark the wrapped serve continuous: true. It has no configurations, so use serve-static for a production-like local run. Give the wrapped build cache: true and outputs ({workspaceRoot}/dist/apps/<app>). Cache hits start from the second build.
  • Move the dependsOn: ['shell:serve'] the generator renamed onto serve-original back to the new serve. When a second host uses the same remotes, name every app in the run-many.
  • Replace each app's whole serve-static target, defaultConfiguration included, with the one from Path A Step 6 using staticFilePath: dist/apps/<app>/browser, and point the e2e webServer commands at nx run-many -t serve-static as in that step.
  • Remove the @nx/angular:webpack-browser entry from targetDefaults in nx.json once every app has moved.
  • Rewrite host routes to use Native Federation's loadRemoteModule. Static hosts replace each import('remoteA/Routes') with the call below, and so do hosts calling loadRemote('remoteA/Routes'). Hosts on @nx/angular/mf swap only the import:
apps/shell/src/app/app.routes.ts
import { loadRemoteModule } from '@angular-architects/native-federation';
export const appRoutes: Route[] = [
{
path: 'remoteA',
loadChildren: () =>
loadRemoteModule('remoteA', './Routes').then((m) => m.remoteRoutes),
},
];
  • Last, once every app has moved and no target names a webpack executor, drop the remoteA/Routes entries from tsconfig.base.json paths and npm remove @nx/module-federation @module-federation/enhanced @nx/webpack webpack-merge. Removing them earlier breaks the graph commands the other steps need.

Builds land under dist/apps/<app>/browser/ with a remoteEntry.json per app and no mf-stats.json. Validate as in Path A Step 7, running the parity check against remoteEntry.json:

  • Every baseline npm shared entry appears as a singleton with the same requiredVersion range.
  • sharedMappings libraries show up as ~<version> with strictVersion.
  • The container name is the project name as written, not normalized.
  • Remote URLs are in main.ts or the manifest.

shareAll adds more shared packages than the old mf-stats.json listed. Trim them with its skip option if they matter to you.

  • --devRemotes selection. Which remotes run live and which serve from a build is now the set of targets you start.
  • Automatic remote fallback. A host fails the request when a remote is down. Add your own error handling if you need it.

For anything beyond this migration, use the official Module Federation configuration reference and, for Path B, the Native Federation documentation.

Last updated: