Monorepos and Turborepos: A Complete Guide and Setup Walkthrough

Monorepos and Turborepos: A Complete Guide and Setup Walkthrough
As teams scale and applications grow more complex, codebases start to fragment. A frontend lives in one repo, the backend in another, shared utilities in a third, and deployment scripts in a fourth. While this modularity has its perks, it can quickly turn into a mess of dependencies and version mismatches. This is where Monorepos step in, and more recently, Turborepos have taken that idea to the next level.
In this article, we’ll cover:
- What is a Monorepo?
- Monorepo vs Polyrepo
- The problems Monorepos solve
- What is Turborepo?
- Benefits of Turborepo
- How to set up a Turborepo from scratch
- Common pitfalls and tips
🔍 What is a Monorepo?
Monorepo stands for Monolithic Repository — a software development strategy where multiple projects (packages, apps, libs) live inside a single version-controlled codebase (like a Git repo). These could be frontend apps, backend services, design systems, CLI tools, etc.
Instead of having:
github.com/org/frontend
github.com/org/backend
github.com/org/ui-library
…you’d have one:
github.com/org/monorepo
With a folder structure like:
/apps
/frontend
/backend
/packages
/ui-library
/utils
Each part is still modular — but it’s all in one repo.
🆚 Monorepo vs Polyrepo
Feature Monorepo Polyrepo Repos One shared One per project Tooling Needs monorepo-aware tools Standard tools work fine Dependencies Easier to share Harder to manage shared code CI/CD Smarter with caching (like turbo) Repeated work across repos Scaling Teams Good for tight collaboration Better for completely isolated teams
🤔 Why Monorepo?
Some reasons why monorepos became popular:
- Shared Code: Easily import internal libraries without npm publishing.
- Atomic Changes: Update backend and frontend together in one commit.
- Unified Tooling: One ESLint/TS config, one versioning strategy.
- Better Developer Experience:
pnpm install
just works. No version hell. - Code Visibility: Everyone sees everything, encouraging cross-team collab.
But managing builds and caching in large monorepos can be hard — that’s where Turborepo comes in.
⚡ What is Turborepo?
Turborepo is a high-performance build system for JavaScript/TypeScript monorepos, created by Vercel.
It introduces a blazing-fast caching system that understands your project’s dependency graph and only rebuilds what’s necessary. It's built for frameworks like Next.js, React, Node.js, and works especially well with pnpm.
Features:
- Remote & local caching
- Parallel execution
- Incremental builds
- Built-in task pipelines
- Compatible with pnpm, npm, yarn
🚀 How to Set Up a Monorepo with Turborepo
Let’s go hands-on and build a modern monorepo setup with:
pnpm
as package managerturbo
as build system- A
frontend
app (React) - A
backend
app (Express) - A shared
ui
package
✅ Step 1: Initialize the Monorepo
mkdir my-monorepo && cd my-monorepo
pnpm init
Install turbo:
pnpm add -D turbo
Setup basic folder structure:
mkdir apps packages
✅ Step 2: Create pnpm-workspace.yaml
This tells pnpm
to treat all subfolders as part of the same workspace.
# pnpm-workspace.yaml
packages:
- apps/*
- packages/*
✅ Step 3: Add a Frontend App
We’ll use Vite + React:
cd apps
pnpm create vite frontend --template react-ts
cd frontend
pnpm install
✅ Step 4: Add a Backend App
We’ll use Express:
cd ../../apps
mkdir backend && cd backend
pnpm init -y
pnpm add express
Create index.js
:
const express = require("express")
const app = express()
app.get("/", (req, res) => res.send("Hello from backend"))
app.listen(3000, () => console.log("Server running on 3000"))
✅ Step 5: Create Shared UI Package
cd ../../packages
mkdir ui && cd ui
pnpm init -y
Add a simple button:
// packages/ui/Button.tsx
import React from "react"
export const Button = () => {
return <button>Shared Button</button>
}
Set up exports in package.json
:
{
"name": "ui",
"version": "1.0.0",
"main": "Button.tsx"
}
✅ Step 6: Link the Shared UI in Frontend
cd ../../apps/frontend
pnpm add ui --filter ./packages/ui
Now import your button in the React app.
✅ Step 7: Add Turborepo Configuration
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false
}
}
}
Each build
task depends on its dependencies’ build. You can now run:
pnpm turbo run build
…and only the necessary packages will build. You can add dev
scripts for parallel development.
✅ Step 8: Add Scripts
Update package.json in root:
{
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"clean": "turbo run clean"
}
}
And in each app:
// apps/frontend/package.json
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
// apps/backend/package.json
{
"scripts": {
"dev": "node index.js",
"build": "echo 'No build needed'"
}
}
🧠 Tips and Gotchas
- Use
pnpm
for better hoisting and workspace performance - Make sure your shared packages don’t import from app-specific code
- You can use
turbo run build --filter=frontend
to build just one app - Add remote caching (Vercel + GitHub integration) for team setups
📦 Real-World Use Cases
- Vercel: Turborepo powers their dashboard and open-source tools
- Shopify: Monorepos for UI and backend services
- Meta: One giant monorepo with millions of files
🔗 Resources
Here are some great links to go deeper:
- Official Turborepo Docs
- Monorepo vs Polyrepo - Nx Blog
- PNPM Workspaces Guide
- Vercel Turborepo Video Intro
- Why You Need a Monorepo (by Lee Robinson)
🧾 Final Words
Monorepos simplify collaboration, code sharing, and consistency across projects. With tools like Turborepo and pnpm, managing them becomes fast and scalable. You don’t need to reinvent the wheel — just structure things cleanly, define your pipeline, and let turbo
do the heavy lifting.