How-To2026-09-0411 min read

How to Set Up a Monorepo in GitHub

A monorepo consolidates multiple projects, services, and shared libraries into a single Git repository. Done right, it eliminates version mismatches between packages, simplifies cross-project refactoring, and ensures that a change to a shared library is tested against every consumer in the same PR. Done wrong, it creates a build system nightmare where every commit triggers every CI pipeline.

GitHub supports monorepos well with features like CODEOWNERS, path-based CI triggers, and branch protection per path. Combined with a workspace manager like Turborepo, npm workspaces, or pnpm workspaces, you can have a monorepo that scales to hundreds of packages without sacrificing developer productivity. This guide covers the full setup from directory structure to CI optimization.

Step-by-step guide

01

Plan your directory structure

Design a top-level directory structure that separates deployable applications from shared packages. The standard convention is 'apps/' for independently deployable services (API, web, workers) and 'packages/' for shared libraries (UI components, types, config, utilities). Add a root-level 'package.json' that defines workspace paths and shared dev dependencies like TypeScript and ESLint.

  • Create 'apps/' directory for deployable services
  • Create 'packages/' directory for shared libraries
  • Set up root package.json with workspace definitions
02

Configure workspace package management

Choose a workspace-aware package manager. pnpm workspaces are the most efficient for disk space and dependency isolation. Add a 'pnpm-workspace.yaml' (or equivalent for npm/yarn) listing your workspace directories. Configure each package's 'package.json' with proper 'name' fields using a scope prefix like '@myorg/api' and internal cross-references using 'workspace:*' protocol for shared dependencies.

03

Set up Turborepo for build orchestration

Install Turborepo at the root of your monorepo. Configure 'turbo.json' with a pipeline that defines task dependencies. For example, 'build' depends on '^build' (topological — build dependencies first), 'test' depends on 'build' of the same package, and 'lint' has no dependencies and runs in parallel. Enable Remote Caching by connecting to Vercel's cache or a self-hosted solution so CI builds skip work that has already been done.

  • Install Turborepo: pnpm add -D turbo
  • Create turbo.json with pipeline task definitions
  • Enable remote caching for CI build speed
04

Configure GitHub Actions for path-based CI

Create CI workflows that trigger only when relevant paths change. Use GitHub Actions' 'paths' filter in 'on.push' and 'on.pull_request' to run the API test suite only when 'apps/api/**' or 'packages/shared/**' changes. For the build step, use Turborepo's '--filter' flag with affected package detection so only changed packages and their dependents are built and tested.

  • Create separate workflow files for each app or use a matrix strategy
  • Add 'paths' filters to trigger only on relevant changes
  • Use 'turbo run build test --filter=...[origin/main]' for affected-only runs
05

Set up CODEOWNERS for path-based review

Create a '.github/CODEOWNERS' file that assigns ownership per directory. Map 'apps/api/' to your backend team, 'apps/web/' to your frontend team, and 'packages/shared-types/' to both. This ensures PRs that touch multiple areas automatically request reviews from all relevant teams. Combined with branch protection requiring CODEOWNERS review, it prevents one team from accidentally breaking another team's code.

06

Configure shared tooling at the root

Set up shared TypeScript configuration, ESLint rules, and Prettier settings at the root level. Create 'packages/config/' with base configurations that individual packages extend. This ensures consistent code style and type checking across the entire monorepo while allowing individual packages to add project-specific rules. Use TypeScript project references for fast incremental type checking across package boundaries.

  • Create shared tsconfig.base.json at root or in packages/config
  • Set up shared ESLint config that packages extend
  • Configure Prettier at root level for consistent formatting
07

Establish contribution and release workflows

Document the monorepo contribution workflow in a CONTRIBUTING file. Define how developers should add new packages, how cross-package changes should be submitted (single PR with all affected packages), and how versioning works. Use Changesets or a similar tool to manage package versions and generate changelogs. Set up a release workflow in GitHub Actions that publishes changed packages and deploys affected services.

Common mistakes

Not using path-based CI triggers

Running every CI workflow on every commit is the fastest way to make a monorepo painful. A change to a README should not trigger a 30-minute test suite for an unrelated service. Use path filters in GitHub Actions and Turborepo's affected package detection to keep CI times proportional to change scope.

Circular dependencies between packages

When package A imports from package B and package B imports from package A, build order becomes impossible to resolve. Enforce a strict dependency hierarchy: shared packages at the bottom, feature packages in the middle, and apps at the top. Use a lint rule that blocks circular imports across package boundaries.

Hoisting all dependencies to the root

Hoisting every dependency to the root 'node_modules' saves disk space but creates phantom dependencies where a package can import something it does not explicitly depend on. Use pnpm's strict mode or configure your package manager to isolate dependencies. Each package should only be able to import what it declares in its own package.json.

Not setting up remote build caching

Without remote caching, every developer and CI run rebuilds every package from scratch. Turborepo's remote cache means a build that already ran on CI is cached and reused locally and vice versa. The time savings are dramatic, often reducing build times by 60-80% for changes that only touch a few packages.

Tips

Use Turborepo's 'turbo run build --dry' to preview which packages would be built without actually running them, useful for verifying your pipeline configuration.

Set up a GitHub Actions composite action that handles workspace setup (install, cache, Turborepo remote cache auth) so individual workflows do not duplicate boilerplate.

Create a 'packages/tsconfig/' package with base TypeScript configs for different package types (library, app, test) so adding a new package takes minutes, not hours.

Add a CI step that runs 'turbo run build --filter=...[origin/main]' and fails if any package has type errors, ensuring cross-package type safety is always enforced.

How Vantage helps

Vantage integrates with GitHub to understand your monorepo structure. When generating tickets from a PRD, Vantage can identify which packages and services are affected by a change, reference the correct CODEOWNERS for reviewer suggestions, and generate tickets scoped to the right part of the codebase.

Frequently asked questions

Spend less time on setup, more on decisions

Vantage connects your tools and generates specs grounded in real data. Free to start.

Free to start. No credit card required.

Related reading