ProNextJS
    Loading
    lesson

    Creating a Next.js App in a Turborepo Monorepo

    Jack HerringtonJack Herrington

    Turborepo is a lightweight monorepo solution that integrates nicely with Vercel. When you need a monorepo setup for your project, Turborepo is a great choice. Let's walk through the process of creating a new Turborepo project and adding a Next.js application to it.

    Setting Up a New Turborepo Project

    To create a new Turborepo project, use the create-turbo command with pnpm dlx:

    pnpm dlx create-turbo@latest
    

    When prompted, provde a name next-on-turbo along your preferred package manager. In this example, we'll proceed with pnpm.

    Once the project is created, open it in your editor. You'll notice two primary folders in the Turborepo structure: packages and apps.

    The packages folder houses shared packages that can be used across different applications within the monorepo. By default, Turborepo sets up a docs site and a web site inside of the apps directory. The web site is built with Next.js.

    Creating a New Next.js App

    Since the boilerplate Next.js app in Turborepo may not always be up to date with the latest version, let's create a new Next.js application within our monorepo.

    First, remove the docs app as we won't be needing it. Now, navigate to the apps directory in your terminal and use the create-next-app command to generate a new project called main-site:

    cd apps
    pnpm dlx create-next-app@latest main-site --use-pnpm
    

    Select the default options for the Next.js project setup.

    At this point, we have two Next.js apps in our monorepo: main-site and web. Let's clean up the main-site app by removing the unnecessary .gitignore and README.md files.

    Configuring the New Next.js App

    To ensure consistency across our monorepo, we'll port over some configuration files from the web app to our newly created main-site app.

    ESLint Configuration

    Copy the .eslintrc.js file from the web app to the main-site app. This file is preconfigured to use the eslint-config package from the Turborepo project.

    Package Dependencies

    Open the package.json file from the main-site app and add the @repo dependencies that are present in Turborepo's default web app.

    The dependencies section should have @repo/ui, and the devDependencies section should include @repo/eslint-config and @repo/typescript-config:

    // inside package.json
      "dependencies": {
        "@repo/ui": "workspace:*",
        "react": "^18",
        "react-dom": "^18",
        "next": "14.1.4"
      },
      "devDependencies": {
        "typescript": "^5",
        "@repo/eslint-config": "workspace:*",
        "@repo/typescript-config": "workspace:*",
        ...
    

    These dependencies include a shared UI library (ui), a custom ESLint configuration (eslint-config-custom), and a shared TypeScript configuration (tsconfig).

    TypeScript Configuration

    Update the tsconfig.json file in the main-site app to extend the shared TypeScript configuration:

    // inside package.json
    {
      "extends": "@repo/typescript-config/nextjs.json",
      "compilerOptions": {
        "plugins": [
          {
            "name": "next"
          }
        ]
      },
      "include": [
        "next-env.d.ts",
        "next.config.js",
        "**/*.ts",
        "**/*.tsx",
        ".next/types/**/*.ts"
      ],
      "exclude": ["node_modules"]
    }
    

    Installing Dependencies and Launching the App

    With our new app configured, let's install the dependencies by running the following command from the root of the Turborepo project:

    pnpm i
    

    This command will install all the necessary dependencies for the main-site app and link the shared packages.

    Now, remove the web app since we won't be using it. To launch our main-site app, run the following command from the project root:

    pnpm dev
    

    The main-site app will start in development mode. Open your browser and navigate to http://localhost:3000 to see the default Next.js starter page.

    Utilizing Shared UI Components

    One of the key benefits of using a monorepo is the ability to share code among different projects. Let's demonstrate this by using a shared UI component from the ui package in our main-site app.

    The ui package exports a Button component. Open the app/page.tsx file in the main-site app and replace its contents with the following code:

    import { Button } from "@repo/ui/button";
    
    export default function Home() {
      return (
        <main className="p-24">
          <Button
            appName="main-site"
            className="px-5 py-2 rounded-full bg-blue-800 text-white"
          >
            Click Me
          </Button>
        </main>
      );
    }
    

    Save the file and refresh your browser. You should now see a button labeled "Click me". Clicking the button will trigger an alert that says "Hello from main-site app!".

    the button displays

    If you inspect the implementation of the Button component in packages/ui/src/button.tsx, you'll find that it simply renders a button that triggers an alert when clicked.

    // inside packages/ui/src/button.tsx
    
    export const Button = ({ children, className, appName }: ButtonProps) => {
      return (
        <button
          className={className}
          onClick={() => alert(`Hello from your ${appName} app!`)}
        >
          {children}
        </button>
      );
    };
    

    Adding a Clean Script

    As a final step in setting up our Turborepo project, let's add a clean script to easily remove all the node_modules directories from the project, including the root directory and all the apps and packages.

    First, install the rimraf package at the root of the project:

    pnpm add rimraf -D -w
    

    Note that the -w flag is necessary to force the installation of the package at the root level, which is not the typical location for installing dependencies for a monorepo project.

    Next, add the following script to the package.json file at the project root:

    // inside package.json at root
    "scripts": {
      ...
      "clean": "rimraf node_modules */**/node_modules"
    }
    

    Now, whenever you need to remove all the nested node_modules directories and start fresh, simply run pnpm clean.

    By following these steps, you now have a solid foundation for building monorepo applications using Turborepo and Next.js!

    Transcript

    Turbo Repo is one of several different Monorepo solutions. There's also MX as well as Moon Monorepo. Turbo is a very lightweight Monorepo system. It also integrates nicely with Versal. I find that when I just need a Monorepo to work with, Turbo repo is actually quite nice for that.

    So I'm going to show you how to install a Next.js application in a turbo repo. So let's start off by creating a turbo repo. To do that I use create turbo. I'm going to call this Next on Turbo. Personal M&E is PMPM, and then I'll bring that up in VS Code.

    So by default with Turbo Repo, there are two primary folders you want to take a look at. There's packages, there's shared packages, and those packages are shared between applications. In this case, out of the box, you've got a docs site and a website, and the website actually is built using Next. But the boilerplate for TurboRepo isn't always up to date with the most recent version of Next. So I'm going to show you how to do is actually put a new Next application into our app.

    First thing I'm going to do is get rid of the docs app. We don't need that. So now we've only got apps web. Let's go over to our console and we'll go into the app directory. And then we'll use exactly the same create next app command in that directory to create a new project called main site.

    I'll use all the standards. So now we've got main site and web. So there's a couple of things we can get rid of right away. We don't need the get ignore and we don't need the read me, but there's some things that we want to port from the web Next.js applications prebuilt. First off is our ESLint-RC.

    If you notice the ESLint-RC actually reuses the ESLint-Config from the package in the repo. Take a look over here at packages. You'll see the ESLint config and that ESLint config has variants for a library as well as Next. There's also a Vite version that we'll show later on. So that's what we're using inside of this ESLint-rc.js.

    What we want to do is go and take that, copy it wholesale, and just replace this. So let's turn that into JS. Cool. So far, so good. Next time, we're going to want to adjust the package JSON.

    So I'm going to put the main site package JSON on the left, and I'm going to put the web version on the right. They look pretty much identical, but we're going to want to add these dependencies. So repo UI, that has some basic UI components in it that we're going to play with, and then the other repo connections to ESLintConfig and the TypeScript config. So far, so good. Finally, we're going to want to adjust our TS config.

    Again, I'll put the main site TS config, that's the default Next.js next to the web version. So the web version primarily just extends the TypeScript configuration .next file that's over in the packages. So let's go and copy and paste that in. And that'll do it. All right, now up at the top level of our app, I'm gonna wanna do pmpmi.

    That's going to go and then connect our main site app with our packages. It's gonna actually do the work in the package.json. Now let's go and remove the web that we no longer need. So now we only have one site in our TurboRepo app. Let's launch it in dev.

    We can see it popped up on localhost 3000. Good, looks like a nice stock site. So now we're in our UI. So we look over here at Packages, UI. It's got a couple of different components.

    It's got a button, a card, and some code. Let's use button, so it exports button. The idea here being the UI would be a shared repository of UI components. So let's go and try that out. Let's go that into our main site and page, And then I'm going to replace the contents of the homepage, which is an import from that rebuild UI with a button.

    And then we're going to use that button. So let's hit save and see what happens. Hit refresh again. Ah, now we've got a button called Click Me. I hit, I actually click it and I get hello from main site app.

    Is that what we expect? Well, let's go take a look at the implementation of button. So go down here to packages, UI source button. We can see that yes, when you click on the button, all it does is just do an alert. So we're actually going and getting that shared button from that UI repo.

    Now, one more thing that I like to do whenever I set up a turbo repo is set up a clean option. So back up at the top level, I'm going to add rimRef, which is a wrapper around rmRef so we can remove all of the node modules from any nested directories on cleanup. So let me hit return here. And then it complains because they're trying to add a dependency at the root level, which is not necessarily where you ever want to go and add dependencies. But this is actually a dependency for the monorepo itself.

    So to force it, we can simply just add the dash W flag. Now we've got rim ref just on the monorepo itself. We can go back to the package.json of the monorepo and then add a clean script where we use rim-raf, just like we would use RMFR. And we'll remove the node modules that are at the top level, and then all the node modules that are nested inside of the apps and the packages. Let's try this out.

    And now we have a Monorepo that you can just clean anytime you want to go and do a fresh install of all of the dependencies. To be honest with you, a cleanup script is the kind of thing you're going to have when you're managing a monorepo.