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
- yarn
npm install -g nx
yarn global add 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
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
- yarn
npm add @module-federation/nextjs-mf
yarn 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.