A monorepo Next.js setup with Lerna, Storybook, and Turbo is a powerful combination that can streamline your development workflow.
Lerna is a tool for managing multiple packages in a single repository, which helps to keep your code organized and makes it easier to manage dependencies.
In a monorepo Next.js setup, Lerna can be used to manage the dependencies between different packages, making it easier to maintain and update your codebase.
With Lerna, you can create a single repository that contains all of your packages, and use the `lerna init` command to set up a new monorepo project.
Storybook is a tool for building and testing UI components in isolation, which helps to ensure that your components are reusable and consistent across your application.
In a monorepo Next.js setup, Storybook can be used to create a library of reusable UI components that can be easily imported into your application.
By using Storybook, you can create a catalog of UI components that can be easily tested and reused throughout your application.
Turbo is a tool for speeding up your development workflow by prebuilding and caching your application's pages, which helps to reduce the time it takes to develop and deploy your application.
In a monorepo Next.js setup, Turbo can be used to prebuild and cache your application's pages, making it easier to develop and deploy your application.
What is a Monorepo?
A monorepo is a single repository that contains multiple related projects or packages.
Lerna is a tool that helps manage projects with multiple JavaScript projects, similar to yarn workspace. It makes it easy to build all packages separately within a monorepo.
Using a monorepo in Next.js is a shift in direction for software projects, particularly those with large codebases.
There are some key factors that have caused this shift, including factors such as.
Setting Up the Project
To set up your Next.js monorepo, start by creating a new directory for the project in your terminal and running the command to set up the package.json. This is the first step in building the monorepo.
Workspaces and tasks are the building blocks of a monorepo. To create a workspace, you'll need to set up the project's workspaces.
Create a new folder, apps/, at the root of the project to store the Next.js apps. This will help you organize your applications.
Next, let's add the admin and store applications. Once installed, open the package.json file of the admin application and replace the dev script with the next dev command to run on a different port.
Run the development server for both projects with yarn dev to ensure everything works properly. This will help you test your applications.
Configuring the Project
To configure the project, you'll need to create a new directory for the project and set up the package.json file in your terminal. This is the first step in building the monorepo.
In the terminal, enter the following command to create a new directory for the project and set up the package.json file. This will create a new project with the necessary setup for a monorepo.
Next, you'll need to set up the project's workspaces, which are the building blocks of a monorepo.
Configuring Metro Bundler
Configuring Metro Bundler is a crucial step in setting up your project. To configure a monorepo with Metro manually, you need to make sure Metro is watching all relevant code within the monorepo, not just apps/native.
We need to update the metro.config.js file to achieve this. This change will ensure that Metro is aware of the entire monorepo and can bundle it correctly.
Config Updates and Tips
You'll want to create a new tsconfig.json file at the root of your project that both applications can extend.
This file will contain the common TypeScript compiler options that both NestJS and NextJS will use.
Update the tsconfig.json file in both applications to extend the root tsconfig.json file.
You can define a single command at the root of the directory to spin up both applications at the same time by adding a new script called "dev" to the package.json file.
This script will look inside the app/ directory for any scripts called dev and run them in parallel.
To install npm packages, you can use the --filter option with pnpm to avoid switching between directories.
Here are the steps to follow:
You can define environment variables in your NestJS app by going to the Variables tab and adding them there.
This way, you won't get a missing peer dependency error when installing new packages into the NestJS app.
Building with Next.js, Storybook, and Lerna
To configure your project for building with Next.js, Storybook, and Lerna, start by creating a new git repository. This is where all your code will live, and it's essential for managing your project's complexity.
You can then run the init command, which will create a new Lerna repo or upgrade an existing repo to the current version of Lerna. This command is crucial for setting up your monorepo structure.
Lerna will generate a monorepo structure that looks like this:
- packages/
- lerna.json
- next.config.js
- storybook/
- .storybook/main.js
- .storybook/preview.js
This structure is the foundation of your monorepo, and it will serve as the base for your Next.js, Storybook, and Lerna setup.
The benefits of using a monorepo are numerous, but some of the most significant advantages include code sharing and a shared configuration package for linting and formatting. This means you can create a reusable component library that can be used by multiple Next.js apps, and you can also share a configuration package for linting and formatting across your entire monorepo.
Here are the benefits of a monorepo:
- Code sharing; to make a reusable component library that can be used by both Next.js apps
- A shared configuration package for linting and formatting
Building the Front-end
We'll create the front-end package inside the packages directory. So, let's change our directory and install the Next.js application. This will generate the following structure:
* front-end (our Next.js application)
+ pages
+ public
+ styles
+ utils
Please note that while creating the Next.js application, I chose the name front-end. Hence, all the files were installed inside that directory. If you choose a different name for your Next.js application, you'll see that directory instead.
Let's add one script inside our components package's package.json file. This script will be used to run our Next.js application.
We've defined the version 0.0.0 as that's the version defined in the components package's package.json file. This version will be used to reference the components package from the front-end package.
Let's import a dummy function from the components package into our front-end application. This will demonstrate how to connect our front-end package with our components package.
Now, if we visit http://localhost:3000/, we should be able to see the console.log output on the browser's console. This confirms that our front-end package is working as expected.
Integrating Storybook with Our Components
Integrating Storybook with our components is a crucial step in building a reusable component library. We'll start by installing Storybook and building our React components with it.
To do this, we'll add the Storybook dependencies, generate example stories, and add two Storybook scripts. This will get our Storybook application up and running.
Now, let's create a Button component in our components package. We'll import this component in our Button story, replacing the existing Button component with our new one.
Our Button component is now ready to be used in our Storybook application. We can run our Storybook application by executing the Storybook scripts we added earlier.
The Storybook application will now be up and running at http://localhost:6006/, where we can view our Button component.
Here are the steps to integrate Storybook with our components:
- Adds the Storybook dependencies
- Generates example stories
- Adds two storybook scripts
With Storybook integrated with our components, we can now build a reusable component library that projects in apps can make use of.
Adding Styles and Components
Adding styles to your components is a breeze with emotion. You'll need to install the necessary dependencies, which includes emotion, and then update your Button component to use it.
To get started, install emotion by running the command `npm install emotion`. Once installed, you can update your Button component to use emotion for styling.
Here's a quick rundown of the dependencies you'll need to install:
- emotion
With emotion installed, you can update your Button component to use it for styling. This will allow you to write CSS-in-JS and keep your styles organized and maintainable.
Reusable Component Library
Building a reusable component library is a crucial step in modern frontend development. It allows you to create a shared library of components that can be used across multiple projects, making codebases easier to maintain.
You can create a new workspace for your component library by running the command `lerna create packages/shared/ui` at the root of your monorepo.
A reusable component library should include a collection of reusable components, such as buttons, cards, and forms. To create a reusable button component, you can create a new file called `Button.jsx` in the `packages/shared/ui` workspace and enter the following code.
Here are some common components that you might include in your reusable component library:
- Buttons
- Cards
- Forms
- Inputs
- Navigation
To use your reusable component library in your Next.js applications, you'll need to add the `ui` package as a dependency in the workspace's `package.json` file. You can do this by adding the following code to the dependencies field:
```json
"ui": "file:packages/shared/ui"
```
You'll also need to configure your Next.js applications to handle the transpilation of local packages, like the `ui` package. You can do this by installing the `next-transpile-modules` package and adding the following code to your `next.config.js` file.
Adding Styles
Adding styles to your components is a crucial step in making them visually appealing. We'll be using emotion for styling, which we'll install as a dependency.
To add styles, you'll need to install the necessary dependencies. For example, we installed emotion to style our Button component.
The Button component can be updated to include styles. We updated our Button component to add styles using emotion.
You can see the updated Button component in action by visiting your Next.js application on http://localhost:3000/. Our Button component was updated to include styles, and it's visible on this page.
Your Storybook application should also show the new Button component with styles. Our Storybook application was updated to display the new Button component with the added styles.
Troubleshooting and Setup
To set up a monorepo with Next.js, you'll need to install the necessary dependencies, including `lerna` and `yarn workspaces`. This will allow you to manage multiple packages within a single repository.
The `lerna` command `lerna init` will create a `lerna.json` file that configures the monorepo. By default, `lerna` uses `yarn` as the package manager, but you can switch to `npm` if needed.
In a monorepo setup, it's essential to have a clear directory structure to avoid conflicts and make it easier to manage your packages. By following a consistent naming convention, such as using the `packages/` directory, you can keep your project organized.
Running Tasks
Running tasks in a monorepo can be a bit tricky, but don't worry, I've got you covered. To run tasks, you need to configure Turborepo to run the Next.js applications in the apps/ directory.
The turbo.json file is where the magic happens. This file defines the tasks that Turborepo will run on the monorepo. Specifically, the pipeline field defines the tasks that Turborepo will run, and the dev field inside the pipeline object defines a workspace's dev task.
You'll also need to define a script in the scripts field of the package.json file at the root of the monorepo to run the dev server of the Next.js applications. This script should include the --parallel flag to run the dev task of the workspaces in parallel.
Here's a quick rundown of the tasks you can run using Turborepo:
- Turbo run build
- Turbo run lint
- Turbo run dev
You can run these tasks using the turbo CLI, which you can install globally or use with npx. If you're using npm, you can use npx to use the turbo CLI without installing it globally.
Problem
Developing a shared component library with consistent styling that works across both mobile and web applications can be a challenge. The goal is to create a solution that supports the development of both mobile and web applications without duplicating components.
The challenge lies in creating a seamless experience across different platforms. This requires careful planning and execution to ensure consistency in styling and functionality.
The solution to this problem is to use React and React Native to develop a shared component library. This allows for the development of both mobile and web applications without duplicating components.
To get started, you'll need to install the Reanimated pod using the command "Run pod-install". This step is crucial for setting up the shared component library.
Deploying the Project
Deploying to Vercel is a straightforward process, especially since Next.js, Turborepo, and Vercel are all owned by the same company. You can deploy your monorepo as two standalone apps: apps/docs and apps/web.
To deploy to Vercel, you'll need to configure your project by selecting Next.js as the framework. Don't worry, it's easy to do.
Important Steps:
- Configure your project with the correct framework selected.
- Deploy each app as an individual project in Vercel.
- Set up the Ignored Build Step for a monorepo.
For example, when deploying "apps/docs", you'll need to replace the build command with `cd ../.. && turbo run build --filter=docs`. The same command applies to the Ignored Build Step.
Deploying to Vercel
Deploying to Vercel is a straightforward process, especially since our apps are built with Next.js and Turborepo, which are owned by Vercel itself.
To get started, we'll deploy each of our two standalone apps, apps/docs and apps/web, as individual projects in Vercel. This will give us a similar CI/CD setup for both apps.
We'll choose Next.js as the framework, which should be selected by default in the Vercel configuration. If it's not, we can select it from the given options.
After deployment, we need to set up the Ignored Build Step, which is a crucial step for a monorepo like ours. This step is marked as [SUPER IMPORTANT ⚠️] in the Vercel configuration.
Deploying Apps/Docs
Deploying Apps/Docs is a crucial step in getting your project up and running. The project name for deploying "apps/docs" might be different, but for this example, it's "rnd-turborepo-docs".
The root directory for deploying "apps/docs" is "apps/docs". You'll need to update the build command to replace "--filter=web" with "--filter=docs", which is "cd ../.. && turbo run build --filter=docs".
The Ignored Build Step for deploying "apps/docs" is the same as for "apps/web", which is not explicitly stated in the article, but is mentioned as "same command as given above in apps/web".
Caching and Optimization
Building a Next.js app can take quite some time, especially with a monorepo that has multiple Next.js apps.
Remote Caching in Turborepo is a feature that allows you to share the cached outputs of tasks across your entire team and CI/CD pipeline.
This can significantly speed up builds by eliminating the need to re-run tasks that have already been completed by someone else on your team.
Sharing build files with your team is especially useful, and Remote Caching can eliminate the need for re-running tasks.
We can build our apps in our local machine using the remote cache that was recently stored in Vercel.
To do this, we need to authenticate the Turbo CLI with our Vercel account by running npx turbo login.
We'll be redirected to Vercel's authentication page, where we have to be logged in to allow authentication to our Turbo CLI.
Linking our Turborepo to Vercel's Remote Cache involves running npx turbo link.
This will link our repo in our local machine with the remote cache stored in Vercel.
If we delete our local build cache and try again, it will still load the latest cache from Vercel.
This means that we can take advantage of Remote Caching even if we've deleted our local build cache.
Tech Stack
Our tech stack is built around Next.js, a powerful framework for building server-rendered and statically generated React applications.
We're using Turborepo as our monorepo tool, which is an easy-to-use, fast, and effective build system for TypeScript/JavaScript codebases.
Our deployment and CI/CD pipeline is handled by Vercel, which integrates seamlessly with Turborepo.
For package management, we're using npm, which supports Turborepo's workspaces feature for managing multiple packages within a top-level root package.
Here's a quick rundown of our tech stack:
- Framework: Next.js
- Monorepo tool: Turborepo
- Deployment & CI/CD: Vercel
- Package manager: npm
Deploying to Railway
Deploying to Railway is a straightforward process that requires defining some scripts in your monorepo's package.json file. Add the following 4 new scripts: `pnpm build:server`, `pnpm start:server`, `pnpm build:web`, and `pnpm start:web`.
To deploy to Railway, you need to create a service and choose your monorepo's Github project. This will create a new service that will try to deploy your app, but it will fail because you need to add extra config.
Update the service name to "server" and navigate to the build and deploy sections. Add the build command `pnpm build:server` and the start command `pnpm start:server`. Also, set the Watch paths to `/apps/server/**`.
After deploying the NestJS server, you'll need to generate a public domain for it. Go to the Settings tab and press the Generate Domain button. Railway will create a random URL for you.
To deploy the NextJS app, create another service and choose the same Github repo. Rename the service to "web" and add the build command `pnpm build:web` and the start command `pnpm start:web`. Set the Watch paths to `/apps/web/**`.
To expose the NextJS app to the public internet, you'll need to add an environment variable with the deployed server URL. Go into the Variables tab and add the environment variable using your deployed server URL as the value.
Frequently Asked Questions
Is Next.js a monorepo?
Next.js is not inherently a monorepo, but it can be used with a monorepo setup to manage multiple projects and packages. Learn how to set up a monorepo with Next.js in our step-by-step guide.
What is the drawback of monorepo?
Monorepo can be challenging to manage due to its large codebase, requiring more robust tooling and infrastructure. This can also make it harder to isolate changes and track bugs
What is the best monorepo tool for JavaScript?
According to the State of JS 2022 survey, the top favorites for JavaScript monorepo tools are Yarn Workspaces, NPM 7 Workspaces, and Lerna. However, the best tool for you will depend on your specific project needs and preferences.
Sources
- https://buttercms.com/blog/nextjs-storybook-and-lerna-build-a-monorepo-structure/
- https://blog.adebayo.dev/posts/building-a-universal-react-app-with-expo-nextjs-nativewind
- https://blog.logrocket.com/build-monorepo-next-js/
- https://articles.readytowork.jp/monorepo-in-next-js-using-turborepo-with-remote-caching-vercel-bc30ebde8951
- https://www.tomray.dev/nestjs-nextjs-trpc
Featured Images: pexels.com