Skip to main content

Ensono Stacks with NextJs Module Federation

Microfrontends are an architectural pattern for building frontend applications by breaking them down into smaller, loosely coupled, and independently deployable parts called "micro-frontends." Each microfrontend corresponds to a distinct feature or functionality of the application and is developed and deployed independently of the others.

Module Federation with NextJs

Module Federation is a feature introduced in Webpack 5, it enables developers to share code dynamically across different applications at runtime. This feature is particularly beneficial when building microfrontends or decoupled frontend architectures.

@module-federation/nextjs-mf is a plugin that leverages Webpack 5’s Module Federation features, allowing developers to implement Module Federation in NextJs applications.

It aims to enable a federated modules architecture in NextJs, enabling you to split a monolithic NextJs application into smaller, independently deployable parts.

The following guide details the steps of how to set up Module Federation with a Ensono Stacks NextJs application. You can also visit this GitHub repo to view an existing example application

NextJs Module Federation with Ensono Stacks

Prerequisites

Nx

We recommend installing Nx globally, all Nx based commands in this guide are based upon a globally installed Nx package.

npm install -g nx

Node

We recommend using the latest LTS version of Node, you can find the latest LTS version here.

Get Started

Create a new Ensono Stacks and Nx workspace

You can scaffold a brand-new Ensono Stacks and Nx workspace using the @ensono-stacks/create-stacks-workspace package.

Follow the interactive questions creating a new NextJs application named host:

npx @ensono-stacks/create-stacks-workspace@latest
tip

Visit the @ensono-stacks/create-stacks-workspace docs for more information and setup instructions!

This will generate a new Ensono Stacks Nx workspace containing a NextJs host application

First We need to install the NextJs federated modules plugin

npm add @module-federation/nextjs-mf

Create a new header application which will be our remote module, we can do this manually or by using the Nx CLI.

nx g @nx/next:app header --no-appDir

Now we have both our host and header modules, we need to update various config in each application to enable module federation.

Let's update the header next.config with details of our remote header module.

header/next.config:
const NextFederationPlugin = require('@module-federation/nextjs-mf');
const { withNx } = require('@nrwl/next/plugins/with-nx');
const path = require('node:path');

module.exports = withNx({
output: 'standalone',
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../')
},
webpack(config) {
config.plugins.push(
new NextFederationPlugin({
name: 'header',
filename: 'static/chunks/remoteEntry.js',
exposes: {
'./index': './pages/index.tsx',
},
}),
);
return config;
}
});

Create an new env file for the header app with the addresses of our host and header modules.

header/.env.development:
STACKS_PUBLIC_HOST_URL="http://localhost:4200"
STACKS_PUBLIC_HEADER_URL="http://localhost:4300"

Then update the header modules project.json with the port on which the header module will be hosted

header/project.json:
{
"serve": {
"executor": "@nx/next:server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "header:build",
"dev": true,
"port": 4300
}
}
}

Lets update the header module component to make it a bit more readable.

header/pages/index.tsx:
export function Index() {
return (
<>
<div className="wrapper">
<div className="container">
<div id="welcome">
<h1>
Header 👋
</h1>
</div>
</div>
</div>
</>
);
}

export default Index;

Now we need to update the host modules next.config.ts with details of the host and any remote modules.

host/next.config.t s:
const NextFederationPlugin = require('@module-federation/nextjs-mf');
const { withNx } = require('@nrwl/next/plugins/with-nx');
const path = require('node:path');

const remotes = isServer => {
const location = isServer ? 'ssr' : 'chunks';

return {
header: `header@${process.env.STACKS_PUBLIC_HEADER_URL}/_next/static/${location}/remoteEntry.js`,
};
};

module.exports = withNx({
output: 'standalone',
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'),
scrollRestoration: true
},
webpack(config, options) {
config.plugins.push(
new NextFederationPlugin({
name: 'host',
filename: 'static/chunks/remoteEntry.js',
remotes: remotes(options.isServer),
}),
);

return config;
}
});

As with the header module, we also need to add a new env file to the host application

host/.env.development:
STACKS_PUBLIC_HOST_URL="http://localhost:4200"
STACKS_PUBLIC_HEADER_URL="http://localhost:4300"

Also updating the host modules project.json with the correct port number

{
"serve": {
"executor": "@nx/next:server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "host:build",
"dev": true,
"port": 4200
}
}
}

We can then import the header module into the host in the _app.tsx file

host/pages/_app.tsx:
import { AppProps } from 'next/app';
import Head from 'next/head';
import Header from 'header/index';
import './styles.css';

function CustomApp({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<title>Welcome to host!</title>
</Head>
<Header />
<main className="app">
<Component {...pageProps} />
</main>
</>
);
}

export default CustomApp;

Now run the app with the following command

nx run-many --target=serve

We can then visit localhost:4200 and see the header module running inside the host application. Or we can visit localhost:4300 and see the header module hosted independently.