SSR-compatible dynamic remote module loader for Angular + Nx Micro Frontends.
- SSR-compatible remote module loading
- Works with Angular and Nx Micro Frontends
- Simple API for loading remote modules
- Separate implementations for browser and server environments
- First, provide the appropriate
RemoteLoader
implementation in your app module:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RemoteLoader, RemoteLoaderBrowser } from 'ngx-mf-remote-loader';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [
{
provide: RemoteLoader,
useClass: RemoteLoaderBrowser
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
- Use the
remoteLoader
function in your routes:
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { remoteLoader } from 'ngx-mf-remote-loader';
const routes: Routes = [
{
path: 'remote-feature',
loadChildren: remoteLoader('remoteAppName')
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
For SSR applications, you need to provide different implementations for browser and server:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RemoteLoader } from 'ngx-mf-remote-loader';
import { AppComponent } from './app.component';
import { PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [
{
provide: RemoteLoader,
useFactory: () => {
const platformId = inject(PLATFORM_ID);
if (isPlatformBrowser(platformId)) {
const { RemoteLoaderBrowser } = require('ngx-mf-remote-loader');
return new RemoteLoaderBrowser();
} else {
const { RemoteLoaderServer } = require('ngx-mf-remote-loader');
return new RemoteLoaderServer();
}
}
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
For server-side rendering, you need to customize the RemoteLoaderServer
implementation to handle your specific remote modules. There are two main approaches:
//# ngx-mf-remote-loader-server.ts
import { RemoteLoaderServer, RemoteItems } from 'ngx-mf-remote-loader';
export class CustomRemoteLoaderServer extends RemoteLoaderServer {
constructor() {
super();
// Register all your remote modules
this.registerRemoteModule('app1', 'Module', () => import('app1/Module'));
this.registerRemoteModule('app2', 'Module', () => import('app2/Module'));
// Add more registrations as needed
}
// You can also override the load method to add custom logic
load(remoteName: string, remoteModule: RemoteItems): Promise<any> {
// Custom logic here if needed
return super.load(remoteName, remoteModule);
}
}
This approach directly handles the module loading with switch cases:
//# ngx-mf-remote-loader-server.ts
import { RemoteLoader, RemoteItems } from 'ngx-mf-remote-loader';
// Define your specific remote module types for better type safety
type RemotesKey =
| 'app1'
| 'app2'
| 'app3-component';
export class CustomRemoteLoaderServer extends RemoteLoader {
load(remoteName: RemotesKey, remoteModule: RemoteItems): Promise<any> {
switch (remoteName + remoteModule) {
case 'app1Module':
// eslint-disable-next-line @nx/enforce-module-boundaries
return import('app1/Module').then((m) => m.RemoteEntryModule);
case 'app2Module':
// eslint-disable-next-line @nx/enforce-module-boundaries
return import('app2/Module').then((m) => m.RemoteEntryModule);
case 'app3-componentCarouselComponent':
// eslint-disable-next-line @nx/enforce-module-boundaries
return import('app3-component/CarouselComponent');
// Add more cases as needed
default:
throw new Error(`Remote module ${remoteName}/${remoteModule} not found`);
}
}
}
Important: Setting up the module-federation.manifest.json file is a required step for ngx-mf-remote-loader to work properly. This manifest is essential for mapping remote module names to their URLs and is a crucial part of the Nx Module Federation setup that our library depends on.
The manifest file is a simple JSON mapping of remote module names to their URLs:
{
"remote-app-1": "http://localhost:4201",
"remote-app-2": "http://localhost:4202",
"remote-app-3": "http://localhost:4203"
}
Required Step: For ngx-mf-remote-loader to function correctly, you must load the manifest file in your host application and set the remote definitions as shown below:
// main.ts
import { setRemoteDefinitions } from '@nx/angular/mf';
// Load the manifest file at application startup
fetch('assets/module-federation.manifest.json')
.then((res) => res.json())
.then((definitions) => setRemoteDefinitions(definitions))
.then(() => import('./bootstrap').catch((err) => console.error(err)));
// main.ts
import { init } from '@module-federation/enhanced/runtime';
// Example of direct initialization
init({
name: 'host',
remotes: [{
name: 'my-remote-app',
entry: 'http://localhost:4201/mf-manifest.json'
}]
});
// Or loading from a manifest file
fetch('assets/module-federation.manifest.json')
.then((res) => res.json())
.then((definitions) => {
const remotes = Object.entries(definitions).map(([name, entry]) => ({
name,
entry
}));
return init({
name: 'host',
remotes
});
})
.then(() => import('./bootstrap').catch((err) => console.error(err)));
Note:
setRemoteDefinitions
will be removed in Nx 22. It's recommended to migrate toinit()
from@module-federation/enhanced/runtime
.
If your application has calls to loadRemoteModule
inside lazy-loaded NgModules, you might encounter issues with loading federated modules. Here's a workaround that makes remote definitions available globally:
// main.ts
import { setRemoteDefinitions } from '@nx/angular/mf';
fetch('assets/module-federation.manifest.json')
.then((res) => res.json())
.then((remoteDefinitions) => {
setRemoteDefinitions(remoteDefinitions);
// Workaround: Make remote definitions available globally
// This helps with loading federated modules in lazy-loaded NgModules
// See https://github.com/nrwl/nx/issues/27842
Object.assign(globalThis, { remoteDefinitions });
})
.then(() => import('./bootstrap').catch((err) => console.error(err)));
This workaround assigns the remote definitions to the global scope, making them accessible to lazy-loaded modules that need to resolve remote module URLs.
This initialization step is essential for our library to properly resolve and load remote modules.
- Initialization: The host application loads the manifest file and passes it to
setRemoteDefinitions()
- Remote Loading: When
loadRemoteModule()
is called, it:- Looks up the remote's URL in the manifest
- Dynamically loads the JavaScript from that URL
- Returns the requested module or component
You can create environment-specific manifest files for different deployment environments:
/assets/
module-federation.manifest.dev.json
module-federation.manifest.prod.json
module-federation.manifest.json # The active manifest
During the build process, you can copy the appropriate environment-specific manifest to the main module-federation.manifest.json file.
For applications that need to update remote URLs at runtime:
import { setRemoteDefinitions } from '@nx/angular/mf';
// Fetch an updated manifest from your API
fetchUpdatedManifest().then(newDefinitions => {
setRemoteDefinitions(newDefinitions);
// Now loadRemoteModule will use the updated URLs
});
Common issues with module federation manifest:
- 404 Not Found: Ensure the manifest file is correctly copied to the assets folder during build
- Remote Loading Failures: Verify that the URLs in your manifest are correct and accessible
- SSR Errors: For server-side rendering, ensure you've properly configured the server-side loader
Under the hood, the library uses Nx's loadRemoteModule
function, which relies on the manifest definitions:
// RemoteLoaderBrowser implementation
export class RemoteLoaderBrowser extends RemoteLoader {
load(remoteName: string, remoteModule: RemoteItems): Promise<any> {
return loadRemoteModule(remoteName, './' + remoteModule).then((m) => {
return remoteModule === 'Module' ? m.RemoteEntryModule : m;
});
}
}
This approach ensures compatibility with the Nx Module Federation system while providing a more convenient API for Angular applications.
For the best development experience, we strongly recommend using our companion tool nx-mf-remote-loader-generator which automates the creation and configuration of remote components that work seamlessly with this library.
MIT