Nx, Next.js, and Module Federation

The author

Micro Frontends

Nx and monorepos

Next.js

Nx + Next.js

Module Federation

Next.js + Module Federation

About the project

Starting the project

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

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

Generating new applications

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

Running in the development environment

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

Generating new components

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

Installing nextjs-mf

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

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

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

Private dependencies with Vercel

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