This is an alpha, sneak peek of Monorepo Maestros. For this iteration, I'm getting all of my thoughts down. In the future, we'll have better information architecture, graphics, and other awesomeness. Your feedback is welcome!

The root

In the root of your project, there is a minimal set of files that you must have to make sure things work correctly.

  • .gitignore
  • turbo.json
  • package.json
  • pnpm-lock.yaml
  • pnpm-workspace.yaml
  • .gitignore

    A .gitignore file isn't unique to a monorepo but it's still essential to have. There will be many files that we create while we're working locally that don't belong in source control. A minimally effective .gitignore may look like:

    .gitignore
    # dependencies
    node_modules
    .pnp
    .pnp.js

    # testing
    coverage

    # next.js
    .next/
    out/
    build

    # misc
    .DS_Store
    *.pem

    # debug
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*

    # local env files
    .env
    .env.local
    .env.development.local
    .env.test.local
    .env.production.local

    # turbo
    .turbo
    .gitignore
    # dependencies
    node_modules
    .pnp
    .pnp.js

    # testing
    coverage

    # next.js
    .next/
    out/
    build

    # misc
    .DS_Store
    *.pem

    # debug
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*

    # local env files
    .env
    .env.local
    .env.development.local
    .env.test.local
    .env.production.local

    # turbo
    .turbo

    Note: Make sure you add any directories or files you may need for your project.

    turbo.json

    This file is responsible for setting up the tasks that we will run in our monorepo. It will define tasks into "pipelines," ensuring that we create a certain order with our tasks so things run correctly (and fast). A minimal turbo.json with a build task for Next.js applications looks like:

    turbo.json
    {
    "pipeline": {
    "build": {
    "dependsOn": ["^build"],
    "outputs": [".next/**", "!.next/cache/**"]
    }
    }
    }
    turbo.json
    {
    "pipeline": {
    "build": {
    "dependsOn": ["^build"],
    "outputs": [".next/**", "!.next/cache/**"]
    }
    }
    }

    We'll talk more about this file when we make it to monorepo managers but for right now, all we want to remember is: This file is responsible for conducting the symphony.

    package.json

    This is the absolute home of our repository, where it all starts. In our root package.json, we'll do a number of important things:

    package.json
    {
    "name": "my-repo",
    "private": true,
    "scripts": {
    // ...
    },
    "devDependencies": {
    "turbo": "latest"
    },
    "packageManager": "pnpm@8.0.0"
    }
    package.json
    {
    "name": "my-repo",
    "private": true,
    "scripts": {
    // ...
    },
    "devDependencies": {
    "turbo": "latest"
    },
    "packageManager": "pnpm@8.0.0"
    }

    Note: We're using pnpm for the sake of demonstration. You can use your favorite.

    A lockfile

    We'll also need a lockfile for our package manager. The lockfile is responsible for ensuring that the same dependencies are installed whenever someone uses our monorepo.

    If we don't have a lockfile, it would be possible to install different dependencies across developers and CI machines, leading to unexpected behavior. When we commit our lockfile to source control, we'll know that everyone is going to be using the same dependencies so we'll have a more stable system.

    Workspace definition file

    We only need this file when we are using pnpm. npm and Yarn define their workspaces in your root package.json instead. We'll discuss these nuances further in the section on package managers.

    For pnpm monorepos, we need to create a file to tell pnpm where our workspaces are located. If you're using the usual monorepo structure with apps and packages, it will look like:

    pnpm-workspace.yml
    packages:
    - apps/*
    - packages/*
    pnpm-workspace.yml
    packages:
    - apps/*
    - packages/*

    Moving out to workspaces

    With the root of the repository handled, we can move out to creating workspaces to start building the more active parts of our codebase.