Skip to content

The NxModuleFederationDevServerPlugin and NxModuleFederationSSRDevServerPlugin are Rspack plugins that handle the development server for module federation in Nx Workspaces. They aim to provide the same Developer Experience(DX) that you would normally receive from using Nx's module-federation-dev-server executors in a non-executor (Inferred Tasks) project.

To use the plugin, you need to add it to your rspack.config.ts file. You can do this by adding the following to your config.

rspack.config.ts
import { NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';
import config from './module-federation.config';
export default {
...otherRspackConfigOptions,
plugins: [
new NxModuleFederationDevServerPlugin({
config,
}),
],
};
rspack.config.ts
import { NxModuleFederationSSRDevServerPlugin } from '@nx/module-federation/rspack';
import config from './module-federation.config';
export default {
...otherRspackConfigOptions,
plugins: [
new NxModuleFederationSSRDevServerPlugin({
config,
}),
],
};

The NxModuleFederationDevServerPlugin and NxModuleFederationSSRDevServerPlugin will serve the remote applications in via a single file server (using http-server) and proxy requests to the remote applications to the correct port. This allows for a more streamlined development experience when working with module federation. You can learn more about this experience in the Module Federation Technical Overview.

The key difference between NxModuleFederationDevServerPlugin and NxModuleFederationSSRDevServerPlugin is that the latter will handle both browser and server bundles to support Server Side Rendering (SSR). It will also serve the host/consumer application by forking (child_process.fork) the server.js output from the server bundle of the host application.

How applications are deployed depends on the teams and organizational requirements. There are two approaches:

  1. À la carte deployments - Each application is deployed according to a release schedule, and can have different cadences.
  2. Affected deployments - When changes are merged, use Nx to test and deploy the affected applications automatically.

Often times, teams mix both approaches so deployments to staging (or other shared environments) are automatic. Then, promotion from staging to production occurs on a set cadence (e.g. weekly releases). It is also recommended to agree on a process to handle changes to core libraries (i.e. ones that are shared between applications). Since the core changes affect all applications, it also blocks all other releases, thus should not occur too frequently.

You may also choose to fully automate deployments, even to production. This type of pipeline requires good end-to-end testing to provide higher confidence that the applications behave correctly. You will also need good rollback mechanisms in case of a bad deployment.

When figuring out the best deployment strategy, or even how to achieve it with Module Federation and Nx, it is worth understanding what is happening under-the-hood. Each host and remote in your Module Federation system is treated like a separate application. However, when a remote is loaded into a host via Module Federation, Module Federation itself does not make any kind of distinguishment. It only cares that the JS file it is trying to load is available at the pre-specified URL, such that it can make a network request to fetch the JS file.

When working locally, Nx parses the configuration in module-federation.config.ts to identify the local federated projects. Nx then examines the project graph to determine the port on which each project will be served. This information is used to create a URL informing Module Federation where to locate the remote JS files. You'll commonly see the following configuration in your module-federation.config.ts:

module-federation.config.ts
export default {
remotes: ['shop', 'cart']
}

These names match the names of the projects in the workspace. Therefore, Nx can find them in the project graph and determine the information it needs. This usually amounts to Nx creating the following URLs:

shop@localhost:4201
cart@localhost:4202

When it comes deployment to a real server, you'll need to configure the URLs to point to their real location. For example, if you deploy the host and the remotes to three servers, each with their own domain, you'd need to configure that:

shop@https://shop.example.com
cart@https://cart.example.com

The remotes option in the module-federation.config.ts file allows you to do this:

module-federation.config.ts
export default {
remotes: [
['shop', 'https://shop.example.com'],
['cart', 'https://cart.example.com'],
]
}

However, once you make this change, it will no longer work locally. You need some mechanism to change the URLs when you're building for a production environment.

A simple way to achieve this could be to use an environment variable:

module-federation.config.ts
const remotes = process.env.PRODUCTION_DEPLOY ?
[
['shop', 'https://shop.example.com'],
['cart', 'https://cart.example.com'],
] :
['shop', 'cart']
export default {
remotes
}

In your CI/CD pipeline, you can set the PRODUCTION_DEPLOY environment variable to true and then build for production, and the remotes will be configured to point to their real location.

export class NxModuleFederationDevServerPlugin {
constructor(
private _options: {
config: ModuleFederationConfig;
devServerConfig?: NxModuleFederationDevServerConfig;
}
) {
this._options.devServerConfig ??= {
host: 'localhost',
};
}
}
export class NxModuleFederationSSRDevServerPlugin {
constructor(
private _options: {
config: ModuleFederationConfig;
devServerConfig?: NxModuleFederationDevServerConfig;
}
) {
this._options.devServerConfig ??= {
host: 'localhost',
};
}
}
export interface NxModuleFederationDevServerConfig {
/**
* The URL hostname to use for the dev server.
*/
host?: string;
/**
* The port to use for the static remotes.
*/
staticRemotesPort?: number;
/**
* The path to the module federation manifest file when using Dynamic Module Federation.
*/
pathToManifestFile?: string;
/**
* Whether to use SSL for the remote applications.
*/
ssl?: boolean;
/**
* The path to the SSL certificate file.
*/
sslCert?: string;
/**
* The path to the SSL key file.
*/
sslKey?: string;
/**
* The number of parallel processes to use when building the static remotes.
*/
parallel?: number;
/**
* Options to proivde fine-grained control over how the dev server finds the remote applications.
*/
devRemoteFindOptions?: DevRemoteFindOptions;
}
export interface DevRemoteFindOptions {
retries?: number;
retryDelay?: number;
}