Monorepo

Monorepo

The Monorepo module leverages the power of Turborepo, enabling you to manage multiple packages and apps within a single repository. With this approach, you can efficiently organize your web development projects, streamline workflows, and enhance code maintainability.

Key Features

  1. Monolithic Efficiency: Bring all your project packages into one unified workspace, eliminating the need for separate repositories.

  2. Modular Organization: Keep your codebase organized by grouping related packages and modules within your monorepo.

  3. Streamlined Development: Simplify code sharing, testing, and dependencies management across your project packages.

  4. Codebase Consistency: Maintain code consistency and quality across all packages, making it easier to enforce best practices.

While these principles are necessary for tech teams, even solo makers benefit from them. By having code separated into packages, you avoid writing repetitive code.

The modularity brings clarity.

As seen in the Code Structure, there are two main folders in the root of the codebase - apps and packages.

- apps
  -- web
- packages
  -- database
  -- es-lint-config-custom
  -- tailwind-config
  -- tsconfig
  -- ui

Apart from modularity, this structure allows you to deploy each app separately (e.g. web runs on the main domain, while docs runs on a subdomain), which gives you perfect control over the scalability of each app.

package.json

Every app defines its own package.json with its own scripts, dependencies, and devDependencies. It makes the package.json files smaller and thus more maintainable.

There is also a root package.json file that defines project-scope dependencies as well as things like packageManager, workspaces, and engines.

turbo.json

You control how Turborepo behaves by defining turbo.json files.

You can find one at the root of the project:

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  // Add every environment variable used in the repository (e.g. via process.env.VERCEL_URL)
  "globalEnv": ["VERCEL_URL"],
  // Every key in the "pipeline" relates to script names defined in "package.json" files
  "pipeline": {
    "build": {
      "dependsOn": ["^db:generate", "^build"],
      "outputs": [".next/**", "!.next/cache/**"]
    },
    // You can parallelize tasks that don't depend on each other
    // https://turbo.build/repo/docs/core-concepts/monorepos/task-dependencies#dependencies-outside-of-a-task
    "topo": {
      "dependsOn": ["^topo"]
    },
    "lint": {
      "dependsOn": ["topo"]
    },
    "ts": {
      "dependsOn": ["topo"]
    },
    "dev": {
      // This makes sure that "db:generate" runs before "dev" scripts.
      "dependsOn": ["^db:generate"],
      "cache": false,
      "persistent": true
    },
    "db:generate": {
      "cache": false
    },
    "db:push": {
      "cache": false
    }
  }
}

You can also find turbo.json in a package/app itself:

{
  "extends": ["//"],
  "pipeline": {
    "dev": {
      // This needs to be defined on a package-level
      "dependsOn": ["^content#build", "^db:generate"]
    }
  }
}

When you run pnpm run dev, Turborepo runs all dev scripts across the repository (all packages and apps). You usually don't need all packages/apps to run simultaneously, especially when you need to save your computer's resources. For this purpose, you can run a package/app independently:

pnpm run dev --filter=web

Further reading

Read more about Turborepo in official docs.