Monorepo vs Multirepo: Choosing for Client Projects

Dev
Β·Dante Chun

The Repository Structure Dilemma

One of the first things to decide when starting a client project is repository structure. Frontend, backend, admin panel... should these go in one repo or be managed separately?

Initially, I thought "just do whatever's convenient." But after several trial and error experiences, I've established clear criteria based on situations.

Problems with Multirepo

I Started with Multirepo

Project A
β”œβ”€β”€ project-a-frontend/     (separate repo)
β”œβ”€β”€ project-a-backend/      (separate repo)
└── project-a-admin/        (separate repo)

I thought managing each independently would be clean. But reality was different.

Problem 1: Type Sharing Hell

Frontend and backend needed to use the same types, but with different repos, I had to copy-paste.

// project-a-backend/src/types/user.ts
interface User {
  id: string
  email: string
  name: string
}

// project-a-frontend/src/types/user.ts
// Copy and paste... manual sync every time API spec changes
interface User {
  id: string
  email: string
  name: string
}

Every time API specs changed, I had to modify both sides. Of course, mistakes happened, only discovered at runtime.

Problem 2: Dependency Version Mismatch

// project-a-frontend/package.json
"dependencies": {
  "axios": "^1.4.0",
  "date-fns": "^2.30.0"
}

// project-a-backend/package.json
"dependencies": {
  "axios": "^0.27.0",  // Why different?
  "date-fns": "^2.28.0"
}

Over time, dependency versions diverge. Every time I applied security patches, I had to visit all repos.

Problem 3: Deployment Synchronization

Frontend and backend needed to deploy simultaneously, but with different repos, each needed separate deployment. CI/CD pipelines also managed separately.

Deploying one and forgetting the other caused bugs.

Switching to Monorepo

Structure Change

project-a/
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ admin/
β”‚   └── shared/          # Shared code
β”œβ”€β”€ package.json
β”œβ”€β”€ pnpm-workspace.yaml
└── turbo.json

Type Sharing Problem Solved

// packages/shared/src/types/user.ts
export interface User {
  id: string
  email: string
  name: string
}

// packages/frontend/src/api/user.ts
import { User } from "@project-a/shared"

// packages/backend/src/api/user.ts
import { User } from "@project-a/shared"

Define types in one place, import from both sides. When API specs change, just modify shared. TypeScript catches type errors on both sides.

Dependency Integration

# pnpm-workspace.yaml
packages:
  - packages/*

# Root package.json
"devDependencies": {
  "typescript": "^5.0.0",
  "eslint": "^8.0.0"
}

Common dependencies managed at root. Only individual package dependencies in each package.json.

Unified Deployment

# .github/workflows/deploy.yml
jobs:
  deploy:
    steps:
      - name: Deploy Backend
        run: pnpm --filter backend deploy
      - name: Deploy Frontend
        run: pnpm --filter frontend deploy

Sequential deployment in one workflow. No more accidentally deploying just one.

Monorepo Tool Selection

Why I Chose Turborepo

There are several monorepo tools:

  • Nx
  • Lerna
  • Turborepo
  • Yarn/pnpm Workspaces

I use the Turborepo + pnpm combination.

Turborepo advantages:

  • Simple configuration
  • Fast build caching
  • Good Vercel integration
  • Low learning curve
// turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/", "dist/"]
    },
    "dev": {
      "cache": false
    },
    "lint": {},
    "test": {}
  }
}

pnpm Workspaces

# pnpm-workspace.yaml
packages:
  - packages/*
  - apps/*

Installing dependencies between packages:

pnpm add @project-a/shared --filter frontend

Considerations for Client Projects

Handoffs

A major advantage of monorepo is easier handoffs.

❌ Multirepo handoff
"Frontend is this repo, backend is that repo, admin is yet another repo.
Each has different .env settings and deployment methods."

βœ… Monorepo handoff
"Just clone this one repo.
After pnpm install, pnpm dev runs all services."

When handing projects to clients or successor developers, one repo means less to explain and fewer chances for mistakes.

Permission Management

But sometimes multirepo is necessary. When clients want only frontend access, or backend code needs security separation.

Situation: Collaboration with external frontend developer
β†’ Separate frontend to its own repo
β†’ Keep the rest as monorepo

Client Requirements

"Deploy frontend on our servers, and you operate the backend on your servers."

Such requirements mean completely different deployment environments, so multirepo might be better. But code sharing is still needed, so publishing only the shared package to npm is also an option.

My Criteria Summary

Choose Monorepo

  • When I develop both frontend/backend
  • When type sharing is important
  • When unified deployment is needed
  • When simplifying handoffs

Choose Multirepo

  • When multiple developers/teams work independently
  • When permission separation is needed
  • When deployment environments are completely different
  • When clients need access only to specific parts

Actual Project Structure Example

Currently running project structure:

dante-company/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ web/              # Main website
β”‚   β”œβ”€β”€ blog/             # Blog (the site you're viewing now)
β”‚   └── admin/            # Admin page
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ ui/               # Shared UI components
β”‚   β”œβ”€β”€ config/           # ESLint, TS configs
β”‚   └── types/            # Shared types
β”œβ”€β”€ pnpm-workspace.yaml
└── turbo.json

All projects share the same design system and types. When starting a new project, just add under apps/.

Conclusion

Monorepo vs multirepo isn't a matter of "which is better." There's just the appropriate choice based on situation.

When a solo developer handles client projects, monorepo is advantageous in most situations. In terms of type sharing, dependency management, and handoffs.

However, if project scale grows, team grows, or permission separation is needed, it's not too late to split into multirepo then. Splitting from monorepo to multirepo is easier than the reverse.