How To Build A Monorepo In NEXT.js

How To Build A Monorepo In NEXT.js

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!!!

Did you find this article valuable?

Support Quokka Labs' Blogs by becoming a sponsor. Any amount is appreciated!