How to Set Up a Production Monorepo with Turborepo

Introduction to Monorepo Workspaces

In modern software engineering, managing multiple isolated repositories for a single product ecosystem often leads to severe operational friction. Developers face challenges such as duplicate configuration files, complex dependency syncing across projects, and fragmented release cycles. To resolve these friction points, organizations are transitioning to monorepos—a unified repository structure that houses multiple applications, services, and shared libraries.

However, as a monorepo grows, build times can increase drastically, turning simple validation runs into multi-hour processes. Turborepo addresses this scaling bottleneck. Created by Vercel, Turborepo is a high-performance build system for JavaScript and TypeScript codebases.

It is designed to understand the dependencies between workspace projects, using incremental builds and caching to execute tasks in parallel. This guide will walk through setting up a production-ready monorepo with Turborepo, optimizing local development workflows, and establishing robust caching configurations.

Understanding the core architecture of Turborepo requires analyzing how it schedules tasks. Unlike traditional build tools that run commands sequentially across all projects, Turborepo constructs a Directed Acyclic Graph (DAG) of your workspace's execution pipeline. By parsing the 'package.json' dependencies of every project, Turborepo identifies which tasks depend on others and executes them in the most efficient order.

For example, if a web application depends on an internal design system package, Turborepo will compile the design system before building the web app. The key advantage is caching.

If no code has changed in the design system since the last build, Turborepo restores the compiled files from cache instantly, bypassing compilation entirely. This mechanism turns slow build sequences into lightning-fast operations, saving valuable developer time.

Initializing pnpm Workspaces

To initialize our monorepo workspace, we will use 'pnpm', which is highly recommended for managing monorepos due to its speed and disk-space efficiency. At Bramsley Digital Studio, we create a root directory and define a 'pnpm-workspace.yaml' file, which specifies where our projects live. The workspace structure typically separates applications (apps) from shared packages (packages):

packages:
  - 'apps/*'
  - 'packages/*'

Next, we create the folder structure and initialize the root 'package.json'. In this root file, we declare our workspaces and install 'turbo' as a development dependency.

The directory layout will contain an 'apps' directory for our user-facing applications (e.g., 'web' and 'admin') and a 'packages' directory for shared resources (e.g., 'shared-ui', 'tsconfig', and 'eslint-config'). This division ensures clear boundaries between deployable apps and reusable code blocks.

Configuring the Build Pipeline in Turborepo

The core configuration of our monorepo is managed via the 'turbo.json' file located in the workspace root. This configuration defines the pipeline rules, mapping tasks to their outputs and defining cache behavior. Let's look at a production-grade 'turbo.json' setup:

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/", ".next/"]
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

In this configuration, the 'dependsOn' array with the '^build' syntax specifies that a project's build task can only execute after all its upstream dependencies (marked by the caret '^') have successfully built. The 'outputs' array instructs Turborepo which compiled directories to cache.

If the build command runs successfully, the folder outputs are saved, enabling instant retrieval on future runs unless source files are mutated. The 'dev' command is configured with 'cache: false' and 'persistent: true' to prevent caching during active local development, where code changes continuously.

  • Task Pipelines: Defines task dependencies and relationships using the dependsOn key.
  • Output Caching: Caches build assets in output directories like dist and .next.
  • Persistent Workers: Configures local dev tasks as non-cacheable persistent services.

Structuring Shared Code and Internal Packages

To make the monorepo functional, we must structure our shared libraries so applications can import them easily. For instance, in our 'packages/shared-ui' directory, we configure a package that exports common React components. Its 'package.json' names the package '@repo/shared-ui' and defines its entry point.

In our web application ('apps/web'), we add '@repo/shared-ui' to the dependencies list. Thanks to pnpm workspaces, pnpm symlinks the package locally, allowing developers to make changes to the shared library and see them reflected immediately in the application without running manual publishing steps. This layout enables high code reuse and structural consistency across all frontend assets.

Accelerating CI/CD with Remote Caching

One of Turborepo's most powerful capabilities is Remote Caching. While local caching speeds up individual developer workflows, remote caching shares those build savings across the entire organization and CI/CD pipelines. When configured, Turborepo uploads build artifacts to a secure remote storage provider.

When a teammate or CI runner pulls the latest code, Turborepo checks the remote cache first. If a match is found, the pre-built files are downloaded, bypassing the build step entirely. This feature is particularly useful in large organizations with dozens of developers, as it reduces continuous integration wait times from minutes to seconds, improving deployment speed and developer experience.

Centralizing Code Quality Standards

As the codebase scales, maintaining linting and code quality standards across multiple packages can become challenging. A recommended pattern is centralizing ESLint, Prettier, and TypeScript configurations within the 'packages' directory. For example, we can create '@repo/eslint-config' and '@repo/tsconfig' packages containing base config files.

Other projects in the monorepo can then import and extend these base configurations in their local configuration files. This ensures that styling and code conventions are enforced uniformly across every application and package, reducing code review friction and maintaining code quality.

Turborepo Monorepo Optimization at the Edge with Bramsley

Configuring a production-grade monorepo requires careful planning around dependency management, cache tuning, and shared pipelines. Misconfigured workspaces can lead to dependency conflicts, cache misses, and slow local development loops. At Bramsley, we architect high-performance Turborepo workspaces for enterprises.

At Bramsley, our engineering team specializes in setting up optimized remote caching, streamlining dependency structures, and building robust CI/CD pipelines that scale with your growth. Contact us to transform your multi-repo setup into a fast, modern monorepo built for the edge.

Bramsley Digital Studio

Enterprise Digital Architecture

We engineer digital infrastructure that drives measurable B2B growth. Experts in Legacy System Migration and High-Performance Frontends.

Architecture Specs & Case Studies

Scale Your Operations

  • Legacy System Migration
  • Scalable Infrastructure
  • High-Performance Frontends
  • Global Edge Deployment