Migrate server-side rendered Angular Module Federation apps Migrate the server build of each server-side rendered Angular Module Federation app in this workspace off the Nx Module Federation APIs that Nx v24 removes. An app with a `server` target or a `webpack.server.config.*` file is server-side rendered. Complete Path A of https://nx.dev/docs/kb/migrate-angular-module-federation for the browser build first, then follow every step at https://nx.dev/docs/kb/migrate-angular-module-federation-ssr.md. Native Federation is not covered, so stop and report if the user asks for it on a server-side rendered app.
Rules that override any shortcut you are tempted to take:
1. Before editing, keep the baseline stats from `dist/apps/<app>/browser/mf-stats.json` and `dist/apps/<app>/server/mf-stats.json`. Record the server-rendered HTML of each host route that renders a remote.
2. Replace `withModuleFederationForSSR` from `@nx/module-federation/angular` with `ModuleFederationPlugin` from `@module-federation/enhanced/webpack`. Keep `@module-federation/node` as a dependency.
3. The server container sets `library: { type: 'commonjs-module' }`, `remoteType: 'script'`, `experiments: { asyncStartup: true }`, and `runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')]`. Its remote URLs keep the `name@` prefix and point at `<origin>/server/remoteEntry.js`, even though the browser container is `.mjs`.
4. Copy the server `shared` map from the server stats.
5. The server build fails after Path A until the server configs are replaced, because the old `webpack.server.config.ts` imports the new browser `module-federation.config.js`. Only validate the server build at the end.
6. Swap `@nx/angular:module-federation-dev-ssr` for `@angular-devkit/build-angular:ssr-dev-server` with an explicit `port`. A static run is `node dist/apps/<app>/server/main.js`, never `@nx/web:file-server`.
7. Accept the migration only when each host route's HTML holds the remote's markup and client scripts, and the page hydrates in a browser without errors. Report what you verified and what you did not.
Reference: https://nx.dev/docs/kb/migrate-angular-module-federation-ssr.md
Migrate the server build of each server-side rendered Angular Module Federation app in this workspace off the Nx Module Federation APIs that Nx v24 removes. An app with a `server` target or a `webpack.server.config.*` file is server-side rendered. Complete Path A of https://nx.dev/docs/kb/migrate-angular-module-federation for the browser build first, then follow every step at https://nx.dev/docs/kb/migrate-angular-module-federation-ssr.md. Native Federation is not covered, so stop and report if the user asks for it on a server-side rendered app.
Rules that override any shortcut you are tempted to take:
1. Before editing, keep the baseline stats from `dist/apps/<app>/browser/mf-stats.json` and `dist/apps/<app>/server/mf-stats.json`. Record the server-rendered HTML of each host route that renders a remote.
2. Replace `withModuleFederationForSSR` from `@nx/module-federation/angular` with `ModuleFederationPlugin` from `@module-federation/enhanced/webpack`. Keep `@module-federation/node` as a dependency.
3. The server container sets `library: { type: 'commonjs-module' }`, `remoteType: 'script'`, `experiments: { asyncStartup: true }`, and `runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')]`. Its remote URLs keep the `name@` prefix and point at `<origin>/server/remoteEntry.js`, even though the browser container is `.mjs`.
4. Copy the server `shared` map from the server stats.
5. The server build fails after Path A until the server configs are replaced, because the old `webpack.server.config.ts` imports the new browser `module-federation.config.js`. Only validate the server build at the end.
6. Swap `@nx/angular:module-federation-dev-ssr` for `@angular-devkit/build-angular:ssr-dev-server` with an explicit `port`. A static run is `node dist/apps/<app>/server/main.js`, never `@nx/web:file-server`.
7. Accept the migration only when each host route's HTML holds the remote's markup and client scripts, and the page hydrates in a browser without errors. Report what you verified and what you did not.
Reference: https://nx.dev/docs/kb/migrate-angular-module-federation-ssr.md
Migrate the server build of each server-side rendered Angular Module Federation app in this workspace off the Nx Module Federation APIs that Nx v24 removes. An app with a `server` target or a `webpack.server.config.*` file is server-side rendered. Complete Path A of https://nx.dev/docs/kb/migrate-angular-module-federation for the browser build first, then follow every step at https://nx.dev/docs/kb/migrate-angular-module-federation-ssr.md. Native Federation is not covered, so stop and report if the user asks for it on a server-side rendered app.
Rules that override any shortcut you are tempted to take:
1. Before editing, keep the baseline stats from `dist/apps/<app>/browser/mf-stats.json` and `dist/apps/<app>/server/mf-stats.json`. Record the server-rendered HTML of each host route that renders a remote.
2. Replace `withModuleFederationForSSR` from `@nx/module-federation/angular` with `ModuleFederationPlugin` from `@module-federation/enhanced/webpack`. Keep `@module-federation/node` as a dependency.
3. The server container sets `library: { type: 'commonjs-module' }`, `remoteType: 'script'`, `experiments: { asyncStartup: true }`, and `runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')]`. Its remote URLs keep the `name@` prefix and point at `<origin>/server/remoteEntry.js`, even though the browser container is `.mjs`.
4. Copy the server `shared` map from the server stats.
5. The server build fails after Path A until the server configs are replaced, because the old `webpack.server.config.ts` imports the new browser `module-federation.config.js`. Only validate the server build at the end.
6. Swap `@nx/angular:module-federation-dev-ssr` for `@angular-devkit/build-angular:ssr-dev-server` with an explicit `port`. A static run is `node dist/apps/<app>/server/main.js`, never `@nx/web:file-server`.
7. Accept the migration only when each host route's HTML holds the remote's markup and client scripts, and the page hydrates in a browser without errors. Report what you verified and what you did not.
Reference: https://nx.dev/docs/kb/migrate-angular-module-federation-ssr.md
A host or remote generated with --ssr builds the same container twice, once for the browser and once for Node. The server build needs its own container format, runtime plugin, and serve targets. Native Federation server-side rendering is not covered.
What replaces what
Section titled “What replaces what”| Going away in v24 | Replacement |
|---|---|
withModuleFederationForSSR from @nx/module-federation/angular | ModuleFederationPlugin with the @module-federation/node runtime |
@nx/angular:module-federation-dev-ssr | @angular-devkit/build-angular:ssr-dev-server |
Capture the server baseline
Section titled “Capture the server baseline”With --ssr, copy the browser stats from dist/apps/<app>/browser/mf-stats.json and the server stats from dist/apps/<app>/server/mf-stats.json.
Also save the HTML each host route returns with curl to compare against at the end.
Server container settings
Section titled “Server container settings”The server container is CommonJS and loads remotes over HTTP through the Node runtime plugin. Each remote's Express server serves its server build under /server, and the host's server-side remote URLs point there. Keep that static mount in every remote's server.ts.
A host or remote generated with --ssr has a server target on @nx/angular:webpack-server whose webpack.server.config.ts calls withModuleFederationForSSR. That config imports the browser federation config, so the server build fails after Path A until you finish the steps below.
Server federation config
Section titled “Server federation config”Write a module-federation.server.config.js per app. It keeps the browser config's name, exposes, and dts: false. Unlike the browser container, it is CommonJS and its remote URLs keep the name@ prefix and end in .js:
const isProd = process.env.NX_TASK_TARGET_CONFIGURATION === 'production';
module.exports = { name: 'shell', filename: 'remoteEntry.js', library: { type: 'commonjs-module' }, remoteType: 'script', dts: false, remotes: { remoteA: isProd ? 'remoteA@https://remote-a.example.com/server/remoteEntry.js' : 'remoteA@http://localhost:4201/server/remoteEntry.js', }, shared: { // ... every entry the baseline server/mf-stats.json lists }, experiments: { asyncStartup: true }, runtimePlugins: [require.resolve('@module-federation/node/runtimePlugin')],};Copy shared from the server stats. They list @angular/ssr, @angular/ssr/node, express, and cors on top of the browser map.
Server bundler config
Section titled “Server bundler config”Point server.options.customWebpackConfig.path at a webpack.server.config.js:
const { ModuleFederationPlugin,} = require('@module-federation/enhanced/webpack');const { join } = require('node:path');const mf = require('./module-federation.server.config.js');
module.exports = (config) => ({ ...config, target: 'async-node', output: { ...config.output, uniqueName: mf.name }, resolve: { ...config.resolve, modules: [ ...(config.resolve?.modules ?? ['node_modules']), join(__dirname, '../..'), ], }, optimization: { ...config.optimization, runtimeChunk: false }, plugins: [...(config.plugins ?? []), new ModuleFederationPlugin(mf)],});main.server.ts, bootstrap.server.ts, and the routes need no change. Delete webpack.server.config.ts.
Targets
Section titled “Targets”Swap the host's serve-ssr from @nx/angular:module-federation-dev-ssr to @angular-devkit/build-angular:ssr-dev-server. Keep browserTarget and serverTarget, and add an explicit port. Remotes keep their serve-ssr target. Start them together:
nx run-many -t serve-ssr -p shell remoteARestart the host's serve-ssr to pick up a remote's server-side changes.
For a production-like run, start node dist/apps/<app>/server/main.js with PORT set.
Validate the result
Section titled “Validate the result”Request each host route that renders a remote, for example with curl, and check that the response holds the remote's markup and the client <script> tags. Then load it in a browser and check that it hydrates without errors.
Keep @module-federation/node in dependencies when you remove @nx/module-federation.
What the official plugin does not do
Section titled “What the official plugin does not do”- Dev orchestration. The SSR dev-server executor started the remotes with the host and proxied the static ones. Start each app's
serve-ssryourself.