Nx, Next.js, and Module Federation

  • About the author
  • Micro Frontends, Nx, and monorepos
  • Next.js, Nx + Next.js
  • Module Federation, Next.js + Module Federation
  • Starting the project
  • Generating new pages and new applications
  • Running in the development environment
  • Generating new components
  • Installing nextjs-mf
  • Creating hooks
  • Deploying projects on Vercel + private dependencies with Vercel
  • References

The author

Bruno Silva, the Next.js developer of Valor Software with the team’s support, has created the solution described below and this material was originally published on Valor’s blog.

Micro Frontends

You’ve probably heard about microservices and the number of benefits they bring to both the scalability of a backend application and the team that develops it. Now imagine if these same advantages could be brought to the front-end. Well, that’s what we’re going to talk about today.
First, let’s talk about what a micro frontend is and what are its benefits. Micro frontends are a way to organize both an application and the team that develops it. Think about the following: a web application is usually composed of several resources, and depending on the size of the application it depends on certain synchrony between the teams that develop it so that new versions are released for production. Let’s take an example: Imagine that we have an online course sales website and that website has the following features:

  • Institutional page (landing page/marketing)
  • Catalog / advanced search
  • Checkout
  • Courses player page
  • Upload page
  • Forum (for students to interact with tutors)
  • Account Settings pages (for students and tutors)

Nx and monorepos

Well, as we saw earlier it is possible to divide some web applications into micro frontends which, initially, may bring you some questions such as: “But then I need to divide all applications into separate repositories? Imagine the headache it will be to test all this!” and that’s where we’ll talk about mono repositories. Mono repository or Monorepo is a single git repository that seeks to manage all the source code of an application, this brings us a series of advantages and some disadvantages, here are some of them:

Pros

  • Standardization (lint) of the code for the entire team
  • Test management in one place
  • Centralization of dependency management
  • Code reuse between applications due to dependency centralization
  • Transparency as we can see all code from a single workspace

Cons

  • .git folder can end up getting big due to a high number of contributions because the whole team is contributing commits in the same project
  • Increase of build time of some applications depending on the dependency level and size of shared files/data
  • No permission granularity, since the entire team needs to have access to the monorepo, the power to restrict the access of certain users to certain parts of the application is lost Looking at the benefits and the context, I saw that it would be an excellent opportunity to use Nx as a manager for our project. Nx is a monorepo manager with a huge range of plugins to facilitate the creation of new applications, libraries, tests, build execution, lint standardization, centralization, dependency management, and many other features.

Next.js

It is indisputable that currently Next.js is one of the web frameworks that has been gaining more and more adoption in recent times and all this is due to the range of features such as server-side rendering, static optimization, file-system routing, API routes, and data-fetching strategies he proposes. Next.js is an awesome tool, but we’ll assume you already know it and skip to the next part.

Nx + Next.js

According to the Nx team, their development philosophy is very similar to Visual Studio Code’s, in which they focus on maintaining a powerful and generic tool, while extensions, or plugins, are fundamental for increasing your productivity with it. As such, @nrwl/next is the plugin that we will use to create and manage our applications with Next.js within our Nx workspace.

Module Federation

Module Federation is a Webpack 5 feature that has arrived to make it possible to share parts of an application to another at runtime. This makes it possible for multiple applications compiled with webpack to repurpose parts of their code as the user interacts with them, which takes us to the next step.

Next.js + Module Federation

Let’s start with our first example of this article where we talk about an eCommerce application, now imagine that our marketing team decides to create a mega Black Friday campaign and decides to change several parts of our application by inserting different components with dynamic banners, carousels, countdowns, themed offers, etc… this would probably be a headache for all teams responsible for our micro frontend applications since each one would have to implement the new requirements of the marketing team in their projects and that would have to be very well tested and synchronized so that everything went right and nothing could be released ahead of time… Anyway, all this could easily generate a lot of work and a lot of headaches for the team, but that’s where the very powerful Module Federation comes in.

About the project

This project will show, how to create the basis for a fully scalable application both in production and in development. In it, we will see some small examples of how the tools mentioned above can be used.

Starting the project

Initially, we will need to install Nx in our environment to handle the commands needed to manage our monorepo. To do this, open a terminal and run:

code ./nextjs-nx-module-federation
├── apps
│ ├── store (...)
│ └── store-e2e (...)
├── babel.config.json
├── jest.config.ts
├── jest.preset.js
├── libs
├── nx.json
├── package.json
├── package-lock.json
├── README.md
├── tools
│ ├── generators (...)
│ └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json

Generating new pages

The @nrwl/next plugin has several generators, and commands that serve to automate the creation of pages, components, and other common structures in the project.

nx g @nrwl/next:page home --project=store
apps/store/pages/home/index.tsx

Generating new applications

Now we will need to create another application, which we will call “checkout”. Unlike the first application we created together with the workspace, we will need to use the following command to create a new Next.js application in the current workspace:

nx g @nrwl/next:app checkout
├── apps
│ ├── checkout (...)
│ ├── checkout-e2e (...)
│ ├── store (...)
│ └── store-e2e (...)
...

Running in the development environment

To see our changes running, we will need to run the following command in the terminal:

nx run-many --target=serve --all

Generating new components

As we saw earlier, we have the possibility to create structures in our application using the Nx CLI tool, now we are going to create a simple button component in the “checkout” project, that executes the following command:

nx g @nrwl/next:component buy-button --project=checkout
apps/checkout/components/buy-button/buy-button.tsx

Installing nextjs-mf

⚠️ Attention: for the application to work with Module Federation features you need to have access to the nextjs-mf or nextjs-ssr plugin which currently requires a paid license!

  • apps/store/next.config.js
  • apps/checkout/next.config.js You will see that in them we have an Nx plugin being used, we will need to maintain it, for that, make the files of each project similar to these:
  • store/next.config.js
  • checkout/next.config.js You will notice that we have two environment variables being used in this file, we will need to define them in each project so create a “.env.development.local” file in each project and leave each file with the following values:
NEXT_PUBLIC_CHECKOUT_URL=http://localhost:4200 NEXT_PUBLIC_STORE_URL=http://localhost:4300
{
// ...
"targets": {
// ...
"serve": {
// ...
"options": {
"buildTarget": "checkout:build",
"dev": true,
"port": 4300
},
// ...
},
// ...
}
{
// ...
"targets": {
// ...
"serve": {
// ...
"options": {
"buildTarget": "checkout:build",
"dev": true,
"port": 4200
},
// ...
},
// ...
}
module.exports = withFederatedSidecar({
// ...
exposes: {
'./buy-button': './components/buy-button/buy-button.tsx',
},
// ...
})(nxNextConfig);
import dynamic from 'next/dynamic';
const BuyButton = dynamic(
async () => import('checkout/buy-button'),
{
ssr: false,
}
);
export function Page() {
return (
<div className={styles['container']}>
<h1>Welcome to Store!</h1>
<BuyButton onClick={() => alert('Hello, Module Federation!')}>Add to Cart</BuyButton>
</div>
);
}

Creating hooks

One of the powers of nextjs-mf is the federation of functions, including hooks. An important detail is that we cannot import hooks asynchronously, which leads us to adopt a solution where we import functions using “require” and the page or component that uses the hook being loaded lazily/asynchronously, what we call “top-level-await”.

import { useState } from 'react';

export default function useAddToCartHook() {
const [itemsCount, setItemsCount] = useState<number>(0);
return {
itemsCount,
addToCart: () => setItemsCount((i) => i + 1),
clearCart: () => setItemsCount(0),
};
}
import { useState } from 'react';

export default function useAddToCartHook() {
const [itemsCount, setItemsCount] = useState<number>(0);
return {
itemsCount,
addToCart: () => setItemsCount((i) => i + 1),
clearCart: () => setItemsCount(0),
};
}
// typing for the hook
type UseAddToCartHookType = () => UseAddToCartHookResultType;

// hook function return typing
type UseAddToCartHookResultType = {
itemsCount: number;
addToCart: () => void;
clearCart: () => void;
};

// hook default value
let useAddToCartHook = (() => ({})) as UseAddToCartHookType;

// import the hook only on the client-side
if (process.browser) {
useAddToCartHook = require('checkout/useAddToCartHook').default;
}

export function Page() {
// on server side extracts the values as undefined
// on the client side extracts the hook values
const { itemsCount, addToCart, clearCart } =
useAddToCartHook() as UseAddToCartHookResultType;

return (
<div>
<h1>Welcome to Custom Hook!</h1>

<p>
Item Count: <strong>{itemsCount}</strong>
</p>
<button onClick={addToCart}>Add to Cart</button>
<button onClick={clearCart}>Clear Cart</button>
</div>
);
}

// here you can use the getInitialProps function normally
// it will be called on both server-side and client-side
Page.getInitialProps = async (/*ctx*/) => {
return {};
};

export default Page;
nx g @nrwl/next:page custom-hook --project=store
import dynamic from 'next/dynamic';
import type { NextPage, NextPageContext } from 'next';

// import functions from page in synchronously way
const page = import('../../async-pages/custom-hook');

// lazy import the page component
const Page = dynamic(
() => import('../../async-pages/custom-hook')
) as NextPage;

Page.getInitialProps = async (ctx: NextPageContext) => {
// capture the getInitialProps function from the page
const getInitialProps = ((await page).default as NextPage)?.getInitialProps;
if (getInitialProps) {
// if the function exists, call the function on server-side and client-side
return getInitialProps(ctx);
}
return {};
};

export default Page;

Deploying projects on Vercel

The procedure that we are going to perform now will be done at Vercel, but we can replicate it without much difficulty on other serverless hosting platforms such as Netlify, AWS Amplify, and Serverless with a plugin for Next.js or even in a self-hosted way using Docker with a private server.
We can carry out the process in two ways: by interface or by CLI, but to facilitate the process we will do it by the interface, you just need to host the project on GitHub so that we can import it in a few clicks, once the project is on GitHub you can open this page on Vercel to deploy the first application… exactly, although it’s a monorepo, we’re going to configure everything so that separate deployments are made.
First, we will deploy the “checkout” app because it has fewer dependencies, for that select the repository as in the following image and click on the button to import it:

npx nx build checkout --prod
npx nx build store --prod

Private dependencies with Vercel

If you open the project build logs, you will notice that in both the error is the same, probably something like this

npm ERR! 403 403 Forbidden - GET <https://r.privjs.com/@module-federation%2fnextjs-mf/-/nextjs-mf-3.5.0.tgz> - You must be logged in to install/publish packages.
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy, or
npm ERR! 403 on a server you do not have access to.
npm ERR! A complete log of this run can be found in:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Valor Software

Valor Software

21 Followers

Useful articles from experienced Valor specialists in various spheres of digital development.