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.
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) 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 logbeyond the depth limit. - Use
git blameaccurately (it stops at the depth boundary). - Run
git merge-basefor 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.
Related articles
- Git Git Monorepo Management at Scale
Manage large Git monorepos with sparse checkout, partial clone, subtrees, CODEOWNERS, and scaling strategies that keep performance acceptable as the repo grows.
- CI/CD CI/CD for Monorepos: Turborepo and Nx Pipelines
Build efficient CI/CD pipelines for monorepos using Turborepo and Nx with smart caching, affected-only builds, and parallel execution.
- Git Git Submodules vs Subtrees: Choosing the Right Approach
Compare git submodules and subtrees for managing shared code and nested repositories. Learn workflows, trade-offs, and when to use each approach in real projects.
- Git Git Submodules vs Subtrees Explained
Compare git submodules and subtrees for managing nested repositories, including workflows, trade-offs, and when to choose each approach in practice.