Skip to content
Codeloom
Git

Git Monorepo Strategies: Sparse Checkout, Shallow Clones, and Tooling

Scale Git monorepos with sparse checkout, partial clones, shallow clones, and modern tooling like Turborepo and Nx. Practical strategies for keeping large repos manageable.

·8 min read · By Codeloom
Advanced 14 min read

What you'll learn

  • When a monorepo makes sense and when it does not
  • Using sparse checkout to work with a subset of the repo
  • Shallow and partial clones for faster CI
  • Monorepo tooling: Turborepo, Nx, and Lerna
  • CODEOWNERS, path-based CI, and scaling practices

Prerequisites

  • Intermediate Git knowledge (branching, remotes, config)
  • Experience working on multi-package or multi-service projects

Why Monorepos

A monorepo stores multiple projects, packages, or services in a single Git repository. Google, Meta, Microsoft, and many startups use monorepos because they solve real problems: atomic cross-project changes, unified CI, shared tooling, and simplified dependency management. But monorepos introduce their own challenges: slow clones, overwhelming git log output, and the need for smart tooling. This article covers the Git-level strategies and tooling that make monorepos practical.

monorepo/
packages/
  shared-ui/        (React component library)
  shared-utils/     (utility functions)
apps/
  web/              (Next.js frontend)
  mobile/           (React Native app)
  api/              (Node.js backend)
tools/
  eslint-config/    (shared ESLint config)
  tsconfig/         (shared TypeScript config)
package.json        (workspace root)
turbo.json          (Turborepo config)
Typical monorepo structure

Sparse Checkout: Work on What You Need

In a monorepo with 50 packages, a frontend developer does not need the mobile app or the backend API checked out. Sparse checkout lets you check out only the directories you care about.

Setting up sparse checkout

# Clone the repo (full history, but limited working tree)
git clone --sparse https://github.com/org/monorepo.git
cd monorepo

# Initialize sparse checkout with cone mode (directory-based, faster)
git sparse-checkout init --cone

# Check out only the packages you need
git sparse-checkout set apps/web packages/shared-ui packages/shared-utils

Your working directory now contains only:

monorepo/
  apps/
    web/              (checked out)
  packages/
    shared-ui/        (checked out)
    shared-utils/     (checked out)
  package.json        (root files always included)
  turbo.json

Everything else exists in Git’s object store but is not materialized on disk.

Adding and removing directories

# Add a directory to your checkout
git sparse-checkout add apps/api

# View current sparse checkout config
git sparse-checkout list
# apps/web
# apps/api
# packages/shared-ui
# packages/shared-utils

# Remove a directory (redefine the full set)
git sparse-checkout set apps/web packages/shared-ui

Sparse checkout with existing clones

You can enable sparse checkout on a repo you already cloned:

cd existing-monorepo
git sparse-checkout init --cone
git sparse-checkout set apps/web packages/shared-ui

Files outside the sparse set disappear from the working tree but remain in the repository.

Disabling sparse checkout

git sparse-checkout disable
# All files are restored to the working tree

Shallow Clones: Skip History You Do Not Need

A full clone downloads every commit since the beginning of time. For CI pipelines and quick tasks, that is wasteful. Shallow clones limit history depth.

# Clone with only the last commit
git clone --depth 1 https://github.com/org/monorepo.git

# Clone with the last 10 commits
git clone --depth 10 https://github.com/org/monorepo.git

Limitations of shallow clones

Shallow clones cannot:

  • Run git log beyond the depth limit.
  • Use git blame accurately (it stops at the depth boundary).
  • Run git merge-base for comparing branches.

Deepening a shallow clone

If you need more history later:

# Fetch 50 more commits
git fetch --deepen=50

# Fetch the full history
git fetch --unshallow

Best use case: CI pipelines

Most CI jobs only need the latest code to build and test:

# GitHub Actions example
- uses: actions/checkout@v4
  with:
    fetch-depth: 1    # Shallow clone, fastest possible

For jobs that need to compare with the base branch (to determine which packages changed):

- uses: actions/checkout@v4
  with:
    fetch-depth: 0    # Full history for accurate diff

Partial Clones: The Best of Both Worlds

Partial clones (also called blobless or treeless clones) download commit history but skip large file content until it is needed:

# Blobless clone: download commits and trees, but not file content
git clone --filter=blob:none https://github.com/org/monorepo.git

# Treeless clone: download only commits, fetch trees and blobs on demand
git clone --filter=tree:0 https://github.com/org/monorepo.git

When you check out a file, Git fetches its content on demand. This is slower for the first access but dramatically reduces initial clone time and disk usage.

Combining partial clone with sparse checkout

This is the optimal configuration for large monorepos:

# Fast clone: no blobs downloaded, no extra files checked out
git clone --filter=blob:none --sparse https://github.com/org/monorepo.git
cd monorepo
git sparse-checkout set apps/web packages/shared-ui

# Only the files in apps/web and packages/shared-ui are downloaded

Monorepo Tooling

Git alone is not enough for monorepo workflows. You need tools that understand package dependencies, run tasks intelligently, and cache results.

Turborepo

Turborepo is a build system for JavaScript/TypeScript monorepos. It understands the dependency graph between packages and runs tasks in the optimal order with caching.

# Install Turborepo
npm install --save-dev turbo

Configure turbo.json:

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "lint": {}
  }
}

Run tasks:

# Build all packages in dependency order
npx turbo build

# Build only the web app and its dependencies
npx turbo build --filter=web

# Run tests only for packages that changed since main
npx turbo test --filter=...[main]

Turborepo caches task outputs. If nothing changed in a package, the cached result is used:

 Tasks:    8 successful, 8 total
 Cached:   6 cached, 8 total
 Time:     1.2s

Nx

Nx is a more opinionated build system with support for multiple languages:

# Initialize Nx in an existing monorepo
npx nx init

Run affected commands:

# Run tests only for projects affected by recent changes
npx nx affected --target=test --base=main

# Build only what changed
npx nx affected --target=build --base=main

# Visualize the dependency graph
npx nx graph

Choosing between Turborepo and Nx

Turborepo: Simpler, focused on task running and caching. Best for teams that want minimal configuration and primarily work with JavaScript/TypeScript.

Nx: More features (code generation, dependency graph visualization, plugins for multiple languages). Best for large teams and polyglot monorepos.

Path-Based CI Pipelines

In a monorepo, you do not want every push to trigger CI for all packages. Use path-based filtering to run only relevant jobs.

GitHub Actions

name: Web App CI
on:
  push:
    paths:
      - 'apps/web/**'
      - 'packages/shared-ui/**'
      - 'packages/shared-utils/**'
  pull_request:
    paths:
      - 'apps/web/**'
      - 'packages/shared-ui/**'
      - 'packages/shared-utils/**'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx turbo test --filter=web

Using Turborepo’s filter in CI

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - run: npm ci
      - run: npx turbo test --filter=...[origin/main]

This runs tests only for packages that changed compared to main.

CODEOWNERS for Monorepos

Use a CODEOWNERS file to assign review responsibility by directory:

# .github/CODEOWNERS

# Frontend team owns the web app and shared UI
/apps/web/          @org/frontend-team
/packages/shared-ui/ @org/frontend-team

# Backend team owns the API
/apps/api/          @org/backend-team

# Mobile team owns the mobile app
/apps/mobile/       @org/mobile-team

# Platform team owns shared tooling
/tools/             @org/platform-team
/package.json       @org/platform-team
/turbo.json         @org/platform-team

PRs automatically request reviews from the right team based on which files changed.

Git Configuration for Large Repos

Enable the filesystem monitor

For repos with many files, the filesystem monitor speeds up git status:

git config core.fsmonitor true
git config core.untrackedcache true

Increase pack window for better compression

git config pack.window 25
git config pack.depth 50

Enable commit graph for faster log operations

git commit-graph write --reachable
git config fetch.writeCommitGraph true

Use maintenance scheduling

# Enable background maintenance
git maintenance start

# This schedules hourly commit-graph updates,
# daily prefetch, and weekly gc

When Not to Use a Monorepo

Monorepos are not always the right choice:

  • Independent release cycles. If packages are versioned and released independently with no shared changes, separate repos reduce coupling.
  • Different access controls. If some code is proprietary and some is open source, separate repos enforce boundaries cleanly.
  • Very large binary assets. Git does not handle large binaries well even with LFS. Media-heavy projects may need specialized storage.
  • Small team, few packages. The tooling overhead of a monorepo is not justified for two or three small projects.

Wrap-Up

Git monorepos work at scale when you combine the right Git features with the right tooling. Sparse checkout limits what developers see and download. Partial and shallow clones speed up CI. Turborepo or Nx handle task orchestration, caching, and change detection. CODEOWNERS and path-based CI ensure the right people review the right code and only affected packages are tested. Start with sparse checkout and a build tool, then add optimizations as the repo grows.