"Monorepo 는 그냥 N 개 package.json 파일 있는 Node 프로젝트. 어려운 부분은 구조가 아니라 — install 빠르게 유지하고 빌드 똑똑하게 유지하는 거야."
왜 Multi-Repo 아닌 Monorepo
프로젝트를 여러 repo 로 분할하고 각각 독립적으로 버전 매길 수 있어. 그게 multi-repo path. Scale 하는데 비용 있어:
- Repo 셋 건드리는 변경엔 PR 셋, 릴리스 셋, CI 실행 셋.
- 버전 skew: 앱 A 가 라이브러리 X@1.2 끌어옴, 앱 B 가 X@1.0; 앱 B 의 버그가 앱 A 도 의존하는 X@1.3 에서 고쳐짐.
- 온보딩: repo X clone, 그 다음 Y, 그 다음 Z; 로컬 dev 위한
npm link춤.
Monorepo 가 모든 걸 한 repo 에 유지, 패키지 매니저가 패키지 로컬 link 하게 두고, cross-cutting 변경을 한 PR 에 출하해서 풀어. 비용: tooling 이 단일-repo 프로젝트보다 10x 많은 package.json 파일 처리해야 함.
레이아웃
Canonical 2026 Node monorepo:
my-repo/
apps/
web/ # Next.js app, references @my/ui and @my/utils
api/ # Node service, references @my/utils
packages/
ui/ # React component library
utils/ # Pure TS utilities
config/ # Shared eslint, tsconfig, prettier presets
package.json # "private": true, workspaces declared
pnpm-workspace.yaml
turbo.json # if using Turborepo
pnpm-lock.yaml
apps/* 는 배포 가능한 애플리케이션. packages/* 는 앱이 소비하는 라이브러리. 분할이 rigid 안 함 — 프로젝트에 맞는 이름 픽.
pnpm Workspace — 기초
- pnpm 의 workspace 가 패키지 경계 강제 — 각 패키지가 자기 선언한 의존성만 봐서 패키지 가로질러 phantom transitive import 방지.
- Content-addressable store 가 React 다 쓰는 패키지 10 개 설치해도 React 10 번 복사 안 함 — 한 store 엔트리에서 hardlink.
workspace:^프로토콜 이 패키지web가@my/ui를 publish 된 것처럼 참조, 실제로는 sibling workspace 에서 끌어옴.
workspace: 프로토콜 다시
Track 2 에서 나왔는데 반복할 가치: monorepo 에서 내부 패키지가 workspace: prefix 로 서로 참조:
// apps/web/package.json
{
"dependencies": {
"@my/ui": "workspace:^",
"@my/utils": "workspace:*"
}
}
개발 중: symlink 가 sibling 가리킴. Publish 중: pnpm 이 sibling 의 현재 버전 기반 실제 semver range 로 다시 씀. 같은 import 문 (import { Button } from '@my/ui'), 컨텍스트 따라 다른 resolution.
Turborepo — 빌드 오케스트레이션
Workspace 가 설치 처리. *빌드 순서* 안 처리. turborepo 가 monorepo 의 패키지 그래프 읽고, 어느 빌드가 어느 거에 의존하는지 파악, topological 로 실행. 거기에 공격적 캐싱: packages/utils 가 마지막 빌드 이후 안 바뀌었으면 앱이 의존해도 재빌드 건너뜀.
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {}
}
}
루트에서 pnpm turbo run build 실행; turbo 가 그래프 walk 하고 빌드를 의존성 순서로 실행, .turbo/ 에 출력 캐싱. turbo run build 의 두 번째 실행이 안 바뀐 패키지엔 캐시 hit — 종종 clean rebuild 보다 5-10x 빨라.
Monorepo 하지 말 때
Monorepo 가 공짜 아냐:
- 한 명, 한 앱, 한 라이브러리 — overhead 가 이익 초과.
- 진짜로 lockfile 공유 못 하는 다른 릴리스 cadence (npm-publish 된 라이브러리 + 내부 제품).
- 프로젝트당 엄격 접근 컨트롤 — monorepo 는 repo 접근 가진 누구든 모든 거 봄 뜻.
단일-repo 로 시작. 코드 공유할 진짜 필요 있을 때 패키지 추가. npm link 의 고통이나 버전 skew 가 매일 될 때 monorepo 로 변환. 예측-하고-monorepo 하지 마; 실제 고통에 응답.