10. 모노레포 관리
학습 목표
- 모노레포 개념과 장단점 이해
- Nx, Turborepo를 활용한 빌드 최적화
- 의존성 관리와 코드 공유 전략
- 대규모 모노레포 성능 최적화
목차
- 모노레포 개요
- 모노레포 도구
- Nx 활용
- Turborepo 활용
- 의존성 관리
- CI/CD 최적화
- 연습 문제
1. 모노레포 개요
1.1 모노레포 vs 멀티레포
┌─────────────────────────────────────────────────────────────┐
│ 멀티레포 (Polyrepo) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ frontend │ │ backend │ │ shared-lib │ │
│ │ (repo) │ │ (repo) │ │ (repo) │ │
│ ├────────────┤ ├────────────┤ ├────────────┤ │
│ │ .git/ │ │ .git/ │ │ .git/ │ │
│ │ package.json│ │ package.json│ │ package.json│ │
│ │ src/ │ │ src/ │ │ src/ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 특징: │
│ • 독립적인 버전 관리 │
│ • npm/pypi 등으로 패키지 공유 │
│ • 프로젝트별 권한 관리 용이 │
│ • 의존성 버전 불일치 위험 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 모노레포 (Monorepo) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ my-company (single repo) │ │
│ ├───────────────────────────────────────────────────────┤ │
│ │ .git/ │ │
│ │ package.json (root) │ │
│ │ nx.json / turbo.json │ │
│ │ │ │
│ │ packages/ │ │
│ │ ├── frontend/ │ │
│ │ │ ├── package.json │ │
│ │ │ └── src/ │ │
│ │ ├── backend/ │ │
│ │ │ ├── package.json │ │
│ │ │ └── src/ │ │
│ │ └── shared-lib/ │ │
│ │ ├── package.json │ │
│ │ └── src/ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 특징: │
│ • 단일 저장소에서 모든 코드 관리 │
│ • 원자적 커밋 (여러 패키지 동시 수정) │
│ • 코드 재사용 용이 │
│ • 일관된 도구와 설정 │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 모노레포 장단점
┌────────────────────────────────────────────────────────────────┐
│ 장점 │
├────────────────────────────────────────────────────────────────┤
│ ✓ 코드 공유가 쉬움 │
│ - 공유 라이브러리 즉시 사용 가능 │
│ - 버전 불일치 문제 없음 │
│ │
│ ✓ 원자적 변경 │
│ - 여러 패키지에 걸친 변경을 하나의 커밋으로 │
│ - API 변경과 클라이언트 수정 동시에 │
│ │
│ ✓ 일관성 │
│ - 동일한 린트, 테스트, 빌드 설정 │
│ - 동일한 의존성 버전 │
│ │
│ ✓ 리팩토링 용이 │
│ - 전체 코드베이스에서 검색/수정 │
│ - IDE 지원 (자동 완성, 참조 찾기) │
│ │
│ ✓ 팀 협업 │
│ - 다른 팀의 코드 쉽게 참조 │
│ - 코드 리뷰에서 전체 맥락 파악 │
├────────────────────────────────────────────────────────────────┤
│ 단점 │
├────────────────────────────────────────────────────────────────┤
│ ✗ 저장소 크기 │
│ - Clone 시간 증가 │
│ - CI 캐싱 복잡 │
│ │
│ ✗ 빌드 시간 │
│ - 전체 빌드 시 오래 걸림 │
│ - 영향받는 부분만 빌드하는 최적화 필요 │
│ │
│ ✗ 도구 복잡성 │
│ - 전용 도구 필요 (Nx, Turborepo, Bazel) │
│ - 학습 곡선 │
│ │
│ ✗ 권한 관리 │
│ - 코드별 접근 제어 어려움 │
│ - CODEOWNERS로 부분적 해결 │
│ │
│ ✗ 의존성 충돌 │
│ - 서로 다른 버전 필요 시 문제 │
│ - hoisting 이슈 │
└────────────────────────────────────────────────────────────────┘
1.3 모노레포 사용 사례
주요 모노레포 사례:
• Google - 수십억 줄의 코드, 단일 저장소
• Facebook - React, Jest 등
• Microsoft - Rush.js로 관리
• Uber - Go 모노레포
• Airbnb - JavaScript 모노레포
적합한 경우:
• 여러 앱이 공통 코드 공유
• 마이크로서비스 + 공유 라이브러리
• 같은 팀이 여러 패키지 관리
• API와 클라이언트 동기화 필요
부적합한 경우:
• 완전히 독립적인 프로젝트
• 다른 언어/기술 스택
• 엄격한 접근 제어 필요
• 외부 기여자가 많은 오픈소스
2. 모노레포 도구
2.1 도구 비교
┌────────────────────────────────────────────────────────────────┐
│ 모노레포 도구 비교 │
├──────────────┬──────────────┬──────────────┬──────────────────┤
│ │ Nx │ Turborepo │ Lerna │
├──────────────┼──────────────┼──────────────┼──────────────────┤
│ 개발사 │ Nrwl │ Vercel │ (유지보수) │
│ 언어 │ JS/TS (+기타)│ JS/TS │ JS/TS │
│ 빌드 캐싱 │ ✓ │ ✓ │ ✗ │
│ 원격 캐싱 │ Nx Cloud │Vercel/자체 │ ✗ │
│ 의존성 그래프│ ✓ │ ✓ │ △ │
│ 코드 생성 │ ✓ │ ✗ │ ✗ │
│ 플러그인 │ 많음 │ 적음 │ 적음 │
│ 설정 복잡도 │ 높음 │ 낮음 │ 낮음 │
│ 대규모 적합성│ 매우 좋음 │ 좋음 │ 보통 │
└──────────────┴──────────────┴──────────────┴──────────────────┘
2.2 기본 구조 (npm/yarn/pnpm workspaces)
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
],
"scripts": {
"build": "npm run build --workspaces",
"test": "npm run test --workspaces",
"lint": "npm run lint --workspaces"
},
"devDependencies": {
"typescript": "^5.0.0",
"eslint": "^8.0.0"
}
}
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
my-monorepo/
├── package.json
├── pnpm-workspace.yaml
├── packages/
│ ├── ui/
│ │ ├── package.json
│ │ └── src/
│ └── utils/
│ ├── package.json
│ └── src/
└── apps/
├── web/
│ ├── package.json
│ └── src/
└── api/
├── package.json
└── src/
3. Nx 활용
3.1 Nx 프로젝트 생성
# 새 Nx 워크스페이스 생성
npx create-nx-workspace@latest my-workspace
# 옵션 선택:
# - 통합 모노레포 (integrated)
# - 패키지 기반 모노레포 (package-based)
# - 독립 실행형 앱 (standalone)
# 기존 저장소에 Nx 추가
npx nx@latest init
3.2 Nx 구조
my-workspace/
├── nx.json # Nx 설정
├── workspace.json # (선택) 프로젝트 설정
├── package.json
├── tsconfig.base.json # 공유 TypeScript 설정
├── apps/ # 애플리케이션
│ ├── web/
│ │ ├── project.json # 프로젝트별 설정
│ │ ├── src/
│ │ └── tsconfig.json
│ └── api/
│ ├── project.json
│ ├── src/
│ └── tsconfig.json
├── libs/ # 라이브러리
│ ├── shared/
│ │ ├── ui/
│ │ │ ├── project.json
│ │ │ └── src/
│ │ └── utils/
│ │ ├── project.json
│ │ └── src/
│ └── feature/
│ └── auth/
│ ├── project.json
│ └── src/
└── tools/ # 커스텀 도구
3.3 nx.json 설정
// nx.json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.ts"
],
"sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]
},
"plugins": [
{
"plugin": "@nx/vite/plugin",
"options": {
"buildTargetName": "build",
"serveTargetName": "serve"
}
}
],
"defaultBase": "main"
}
3.4 Nx 명령어
# 프로젝트 그래프 시각화
nx graph
# 특정 프로젝트 빌드
nx build web
# 영향받은 프로젝트만 빌드
nx affected:build --base=main
# 영향받은 프로젝트 테스트
nx affected:test --base=main
# 병렬 실행
nx run-many --target=build --parallel=5
# 모든 프로젝트 실행
nx run-many --target=build --all
# 특정 프로젝트들만
nx run-many --target=test --projects=web,api
# 캐시 상태 확인
nx show project web
# 코드 생성
nx generate @nx/react:component button --project=ui
nx generate @nx/node:application api
nx generate @nx/js:library utils
# 마이그레이션
nx migrate latest
nx migrate --run-migrations
3.5 Nx Cloud (원격 캐싱)
# Nx Cloud 연결
npx nx connect-to-nx-cloud
# 또는 직접 설정
# nx.json에 추가:
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"accessToken": "your-access-token",
"cacheableOperations": ["build", "test", "lint"]
}
}
}
}
4. Turborepo 활용
4.1 Turborepo 설정
# 새 프로젝트 생성
npx create-turbo@latest
# 기존 모노레포에 추가
npm install turbo --save-dev
4.2 turbo.json 설정
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build", "test", "lint"]
}
}
}
4.3 Turborepo 명령어
# 모든 패키지 빌드
turbo build
# 특정 패키지만
turbo build --filter=web
# 의존성 포함
turbo build --filter=web...
# 의존하는 패키지 포함
turbo build --filter=...shared-ui
# 변경된 패키지만
turbo build --filter=[HEAD^1]
# 병렬 실행 제한
turbo build --concurrency=10
# 그래프 시각화
turbo build --graph
# 캐시 없이 실행
turbo build --force
# 드라이 런
turbo build --dry-run
4.4 원격 캐싱 (Vercel)
# Vercel에 연결
npx turbo login
npx turbo link
# 또는 환경 변수로
TURBO_TOKEN=your-token
TURBO_TEAM=your-team
// turbo.json에 추가
{
"remoteCache": {
"signature": true
}
}
4.5 Turborepo 프로젝트 구조
my-turborepo/
├── turbo.json
├── package.json
├── apps/
│ ├── web/
│ │ ├── package.json
│ │ ├── next.config.js
│ │ └── src/
│ └── docs/
│ ├── package.json
│ └── src/
└── packages/
├── ui/
│ ├── package.json
│ └── src/
├── config/
│ ├── eslint/
│ │ └── package.json
│ └── typescript/
│ └── package.json
└── utils/
├── package.json
└── src/
5. 의존성 관리
5.1 내부 패키지 참조
// packages/ui/package.json
{
"name": "@myorg/ui",
"version": "0.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./button": {
"import": "./dist/button.mjs",
"require": "./dist/button.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts"
}
}
// apps/web/package.json
{
"name": "web",
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
5.2 TypeScript 경로 설정
// tsconfig.base.json (root)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@myorg/ui": ["packages/ui/src/index.ts"],
"@myorg/ui/*": ["packages/ui/src/*"],
"@myorg/utils": ["packages/utils/src/index.ts"],
"@myorg/utils/*": ["packages/utils/src/*"]
}
}
}
// apps/web/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"],
"references": [
{ "path": "../../packages/ui" },
{ "path": "../../packages/utils" }
]
}
5.3 버전 관리
# Changesets 사용 (버전 관리)
npm install @changesets/cli -D
npx changeset init
# 변경사항 추가
npx changeset
# 버전 범프 유형 선택: major/minor/patch
# 영향받는 패키지 선택
# 변경 설명 작성
# 버전 업데이트
npx changeset version
# 배포
npx changeset publish
// .changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [["@myorg/ui", "@myorg/utils"]],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
5.4 공유 설정
// packages/config/eslint/index.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
// 공통 규칙
}
};
// apps/web/.eslintrc.js
module.exports = {
root: true,
extends: ['@myorg/eslint-config'],
// 프로젝트별 추가 설정
};
6. CI/CD 최적화
6.1 GitHub Actions 설정
# .github/workflows/ci.yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 전체 히스토리 (affected 계산에 필요)
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Nx 사용 시
- name: Nx affected
run: |
npx nx affected:lint --base=origin/main
npx nx affected:test --base=origin/main
npx nx affected:build --base=origin/main
# Turborepo 사용 시
# - name: Turbo build
# run: pnpm turbo build --filter=[origin/main...]
# 원격 캐싱 (Nx Cloud)
nx-cloud:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Build with Nx Cloud
run: npx nx affected:build --base=origin/main
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
6.2 영향 범위 분석
# Nx - 영향받은 프로젝트 목록
nx show projects --affected --base=main
# 영향받은 파일 목록
nx show projects --affected --base=main --files
# Turborepo - 변경된 패키지만
turbo build --filter=[HEAD^1]
# Git으로 변경 파일 확인
git diff --name-only HEAD^1
# 경로 기반 필터링 (GitHub Actions)
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
web:
- 'apps/web/**'
api:
- 'apps/api/**'
shared:
- 'packages/**'
- if: steps.changes.outputs.web == 'true'
run: pnpm build --filter=web
6.3 캐싱 전략
# GitHub Actions 캐싱
- name: Cache turbo build
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }}
6.4 선택적 배포
# 변경된 앱만 배포
name: Deploy
on:
push:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
web: ${{ steps.filter.outputs.web }}
api: ${{ steps.filter.outputs.api }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
web:
- 'apps/web/**'
- 'packages/**'
api:
- 'apps/api/**'
- 'packages/**'
deploy-web:
needs: changes
if: needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying web..."
deploy-api:
needs: changes
if: needs.changes.outputs.api == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying api..."
7. 연습 문제
연습 1: 모노레포 초기 설정
# 요구사항:
# 1. pnpm workspaces로 모노레포 설정
# 2. 공유 UI 라이브러리 생성
# 3. 웹 앱에서 UI 라이브러리 사용
# 4. TypeScript 경로 설정
# 구조 및 설정 파일 작성:
연습 2: Nx 워크스페이스
# 요구사항:
# 1. Nx 워크스페이스 생성
# 2. React 앱 추가
# 3. Node API 추가
# 4. 공유 라이브러리 생성
# 5. 의존성 그래프 확인
# 명령어 작성:
연습 3: Turborepo 파이프라인
// 요구사항:
// 1. build, test, lint, deploy 태스크 정의
// 2. 적절한 의존성 설정
// 3. 캐시 설정
// 4. 개발 서버 설정
// turbo.json 작성:
연습 4: CI/CD 최적화
# 요구사항:
# 1. 영향받은 프로젝트만 빌드/테스트
# 2. 원격 캐싱 설정
# 3. 변경된 앱만 배포
# 4. 캐싱으로 빌드 시간 단축
# GitHub Actions 워크플로우 작성:
다음 단계
참고 자료
← 이전: 고급 Git 기법 | 목차