A “Monorepo” is a single repository containing multiple kinds of projects or components' apps, tools, and configurations. It's an alternative to creating repositories for each project or part of a project. It is also known as a version-controlled code repository that holds many projects.
While these projects may be related, they are often independent and run by various teams. Currently, various companies host all their code in a single repository, later shared among everyone. Utilizing a monorepo structure for application development can be extremely favorable. A monorepo structure makes dealing with various dependent applications or bundles much easier.
But how can we build a Monorepo in NextJS?
In this blog, you’ll figure out how to build a monorepo using Lerna. We’ll build a Next.js application to import components from a separate package.
Let's start with step by step process!!!
- Firstly, ensure you have installed node package manager (npm) and lerna.
npm -v && lerna -v
- Navigate to the working directory, and enter the below command in the terminal. This will initialize the lerna repo.
lerna init
- Now, your work directory is populated with the below files:
Enter the following command to create and initialize the npm package inside the package folder & follow the instruction to create an npm initialized with package.json inside the shared folder. You can name this package at your convenience.
Remove tests and lib folder. And create an src folder and add index.ts to export from the shared package.
lerna create shared
- After completion, the above package created has package.json as below & updates the “main” property.
{
"name": "@monorepo/shared",
"version": "0.0.0",
"description": "> TODO: description",
"author": "Ujjwol Kayastha <uzol123@gmail.com>",
"homepage": "",
"license": "ISC",
"main": "src/index.ts",
"dependencies": {
"@types/react": "^17.0.4",
"dotenv": "^8.2.0",
"next-images": "^1.7.0",
"typescript": "^4.2.4"
}
}
- Now let’s create the next project inside the packages folder.
cd packages && yarn create next-app
- Now, your work directory looks like the below:
- Now let's add scripts in package.json of the root directory; now the package.json looks like this:
{
"name": "root",
"private": true,
"devDependencies": {
"lerna": "^3.22.1"
},
"scripts": {
"bootstrap": "yarn install; lerna bootstrap;",
"start": "lerna run start --parallel",
"start:user": "node -r ./dotenv.config.js node_modules/.bin/lerna run --scope user --stream dev",
"build:user": "node -r ./dotenv.config.js node_modules/.bin/lerna run --scope user --stream build",
"run:build:user": "lerna run start --scope user",
},
"workspaces": [
"packages/*"
]
}
- Since we are using workspaces, let's add a shared package as a dependency in our next project (user). First, let’s add npmClient and useWorkspaces attributes in the lerna.json file in the root directory.
{
"packages": [
"packages/*"
],
"version": "0.0.0",
"npmClient": "yarn",
"useWorkspaces": true
}
- Now, add the name of the package as a dependency. Now your package.json is shown below:
{
"name": "user",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "10.1.3",
"react": "17.0.2",
"react-dom": "17.0.2",
"@monorepo/shared": "0.0.0"
}
}
- Now add dotenv.config.js file for environment file configuration that includes. For this configuration file to work, add the dotenv package using the lerna add dotenv command.
const dotenv = require("dotenv");
const path = require("path");
if (process.env.NODE_ENV === "production") {
dotenv.config({
path: path.resolve(__dirname, `./.env.production`),
});
} else {
dotenv.config({
path: path.resolve(__dirname, "./.env"),
});
}
- Add the next.config.js file in the root directory of the user package. Also, add the below libraries for the next transpile support.
lerna add next-compose-plugins --scope=user
lerna add next-transpile-modules --scope=user
lerna add next-images --scope=user
Yours next.config.js now looks like this:
const withPlugins = require("next-compose-plugins");
const withTM = require("next-transpile-modules")(["@monorepo/shared"]);
const withImages = require("next-images");
module.exports = withPlugins([withTM(), withImages], {
webpack: (config) => {
// custom webpack config
return config;
},
images: {},
});
- That's it. Now you can start our app using the command: yarn start: user This will run the user package. Also, it gives the URL where it is hosted, like the below:
Initially, the outcome of the process, as mentioned above, looks like this:
Finally, let’s add typescript configurations for the overall project. Enter the below command to add typescript for typescript configuration.
lerna add typescript
lerna add @types/react
- Its time to create a tsconfig.json file in the root directory and add the following code:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
}
- Add tsconfig.json files in the respective packages' root directory. We can use extends property. It is similar to the user package we’ve added the tsconfig file as:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"isolatedModules": true,
"noEmit": true,
"allowSyntheticDefaultImports": true
}
}
- Convert all your .js or .jsx files in the user package to .ts or .tsx accordingly. Your final working directory may look like this:
Change the code inside the index.tsx file in pages inside the user package to see the change.
Now, let's test monorepo by creating a sample button component in the shared package and using it in the user package. Create a folder named components inside the src folder in the user package with the help of a Button component like the one below:
Button.tsx
import React from "react";
export const Button = () => {
return <button>TEST BUTTON</button>;
};
- Export the above component from the component’s index.ts file as
export * from "./Button";
export * from "./components";
- Use the component in the user package. Such as using the Button in the index.tsx as:
import { Button } from "@monorepo/shared";
export default function Home() {
return (
<div>
<Button />
</div>
);
}
The result:
Cool!!! You have successfully created your monorepo from scratch with typescript configuration using Next.js and lerna. The final result will be as follows:
Hence, as per our expert, monorepos will continue to be in trend in the web development community due to their unique benefits and tools that make it easier for developers.
I hope this guide will help you to build a monorepo in Next.js. For any queries, questions, or suggestions, please feel free to connect with us.
Thanks!!!