Skip to content

필사 모드: 모노레포 전략 가이드 2025: Nx vs Turborepo vs Lerna — 대규모 코드베이스 관리의 모든 것

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며: 왜 모노레포인가

Google은 20억 줄의 코드를 하나의 저장소에서 관리합니다. Meta, Microsoft, Uber, Airbnb도 모노레포를 사용합니다. 모노레포는 더 이상 실험적 전략이 아니라, 대규모 소프트웨어 개발의 검증된 패턴입니다.

하지만 모노레포를 잘못 운영하면 CI가 30분 넘게 걸리고, 의존성 지옥에 빠지며, 코드 소유권이 불명확해집니다. 이 글에서는 모노레포의 올바른 도입 전략부터 Nx, Turborepo, Lerna의 심층 비교, CI/CD 최적화, 팀 운영 전략까지 모든 것을 다룹니다.

1. 모노레포 vs 폴리레포

1.1 정의

- **모노레포(Monorepo)**: 여러 프로젝트/패키지를 단일 저장소에서 관리

- **폴리레포(Polyrepo)**: 각 프로젝트를 개별 저장소에서 관리 (=멀티레포)

1.2 비교표

| 항목 | 모노레포 | 폴리레포 |

|------|---------|---------|

| 코드 공유 | 즉시 (같은 저장소) | npm/레지스트리 통해 |

| 의존성 관리 | 통합 관리 | 각 저장소별 독립 |

| 원자적 변경 | 가능 (하나의 PR) | 불가 (여러 PR 필요) |

| CI 복잡도 | 높음 (최적화 필요) | 낮음 (각 저장소 독립) |

| 코드 가시성 | 전체 코드 검색 가능 | 각 저장소 별도 탐색 |

| 릴리스 관리 | 복잡 (Changesets 등) | 단순 (개별 버전) |

| 권한 관리 | CODEOWNERS 필요 | 저장소별 분리 |

| 초기 클론 | 느릴 수 있음 | 빠름 |

| 리팩토링 | 전체 코드에 걸쳐 가능 | 저장소 경계에서 어려움 |

1.3 언제 모노레포를 선택해야 하는가

**모노레포가 적합한 경우:**

- 패키지 간 긴밀한 의존성이 있는 경우

- 공유 라이브러리가 많은 경우

- 원자적(atomic) 변경이 필요한 경우

- 코드 재사용을 극대화하고 싶은 경우

- 팀 간 코드 가시성이 중요한 경우

**폴리레포가 적합한 경우:**

- 완전히 독립된 프로젝트들

- 팀 간 기술 스택이 매우 다른 경우

- 엄격한 접근 제어가 필요한 경우

- 오픈소스와 비공개 코드를 분리해야 하는 경우

2. 대규모 기업의 모노레포 전략

2.1 Google (Piper)

- **규모**: 20억 줄 이상, 86TB

- **도구**: Piper(자체 VCS) + Blaze/Bazel(빌드)

- **핵심**: 모든 코드를 하나의 저장소에서 관리, 트렁크 기반 개발

- **교훈**: 적절한 도구 없이는 불가능. 커스텀 VCS와 빌드 시스템이 필수

2.2 Meta (Buck2)

- **도구**: Mercurial + Buck2(빌드 시스템)

- **핵심**: Virtual filesystem으로 필요한 파일만 로드

- **교훈**: 대규모에서는 파일 시스템 레벨의 최적화가 필요

2.3 Microsoft (1JS)

- **도구**: Git + Rush(빌드 오케스트레이션)

- **핵심**: JavaScript/TypeScript 프로젝트 통합 관리

- **교훈**: 점진적 마이그레이션이 현실적인 전략

2.4 Uber

- **도구**: Go 모노레포 + Buck(빌드)

- **핵심**: 5000개 이상의 마이크로서비스를 단일 저장소에서

- **교훈**: 마이크로서비스와 모노레포는 공존 가능

3. 도구 비교: Nx vs Turborepo vs Lerna vs Rush

3.1 기능 비교표

| 기능 | Nx | Turborepo | Lerna | Rush |

|------|------|-----------|-------|------|

| 태스크 파이프라인 | O | O | O (v7+) | O |

| 로컬 캐싱 | O | O | X | O |

| 원격 캐싱 | O (Nx Cloud) | O (Vercel) | X | O (자체) |

| Affected 감지 | O (프로젝트 그래프) | O (파일 해시) | O (v7+) | O |

| 코드 생성기 | O (generators) | X | X | X |

| 프로젝트 그래프 시각화 | O | X | X | X |

| 프레임워크 플러그인 | O (React, Angular 등) | X | X | X |

| 분산 실행 | O (Nx Agents) | X | X | O |

| 패키지 매니저 | npm, yarn, pnpm | npm, yarn, pnpm | npm, yarn, pnpm | pnpm |

| 학습 곡선 | 높음 | 낮음 | 낮음 | 중간 |

| 오픈소스 | O | O | O | O |

3.2 도구 선택 가이드

프로젝트 시작?

├── 소규모 (패키지 10개 미만)

│ ├── 빠른 시작 원함 → Turborepo

│ └── 코드 생성 필요 → Nx

├── 중규모 (패키지 10-50개)

│ ├── Vercel/Next.js 생태계 → Turborepo

│ ├── 풍부한 플러그인 필요 → Nx

│ └── 기존 Lerna 사용 중 → Lerna v7

└── 대규모 (패키지 50개 이상)

├── 분산 실행 필요 → Nx

└── Microsoft 스타일 → Rush

4. pnpm Workspace 설정

4.1 기본 구조

my-monorepo/

├── pnpm-workspace.yaml

├── package.json

├── .npmrc

├── apps/

│ ├── web/

│ │ └── package.json

│ └── api/

│ └── package.json

├── packages/

│ ├── ui/

│ │ └── package.json

│ ├── utils/

│ │ └── package.json

│ └── config/

│ └── package.json

└── tooling/

├── eslint/

│ └── package.json

└── typescript/

└── package.json

4.2 pnpm-workspace.yaml

packages:

- "apps/*"

- "packages/*"

- "tooling/*"

4.3 루트 package.json

{

"name": "my-monorepo",

"private": true,

"scripts": {

"build": "turbo run build",

"dev": "turbo run dev",

"lint": "turbo run lint",

"test": "turbo run test",

"clean": "turbo run clean"

},

"devDependencies": {

"turbo": "^2.0.0"

},

"packageManager": "pnpm@9.0.0"

}

4.4 패키지 간 참조

{

"name": "@myorg/web",

"dependencies": {

"@myorg/ui": "workspace:*",

"@myorg/utils": "workspace:*"

}

}

`workspace:*`는 로컬 패키지를 직접 참조합니다. npm에 배포할 때 pnpm이 실제 버전으로 교체합니다.

4.5 .npmrc 설정

호이스팅 설정

shamefully-hoist=false

strict-peer-dependencies=false

워크스페이스 설정

link-workspace-packages=true

prefer-workspace-packages=true

5. Nx 심층 분석

5.1 Nx 초기 설정

새 Nx 워크스페이스 생성

npx create-nx-workspace@latest my-monorepo --preset=ts

기존 모노레포에 Nx 추가

npx nx@latest init

5.2 nx.json 설정

{

"targetDefaults": {

"build": {

"dependsOn": ["^build"],

"inputs": ["production", "^production"],

"cache": true

},

"test": {

"inputs": ["default", "^production"],

"cache": true

},

"lint": {

"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],

"cache": true

}

},

"namedInputs": {

"default": ["{projectRoot}/**/*", "sharedGlobals"],

"production": [

"default",

"!{projectRoot}/**/*.spec.ts",

"!{projectRoot}/tsconfig.spec.json"

],

"sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]

},

"nxCloudAccessToken": "your-token-here"

}

5.3 프로젝트 그래프

프로젝트 의존성 그래프 시각화

npx nx graph

특정 프로젝트의 의존 관계 확인

npx nx graph --focus=my-app

영향받는 프로젝트 확인

npx nx affected:graph

프로젝트 그래프 예시:

web-app ───▶ ui-lib ───▶ utils

│ │

▼ ▼

api-app ───▶ shared-types

5.4 Affected 명령 (변경 감지)

변경된 코드에 영향받는 프로젝트만 빌드

npx nx affected -t build

영향받는 프로젝트만 테스트

npx nx affected -t test

base 브랜치 지정

npx nx affected -t build --base=main --head=HEAD

Affected 동작 원리:

1. Git diff로 변경된 파일 감지

2. 프로젝트 그래프에서 해당 파일이 속한 프로젝트 찾기

3. 의존성 그래프를 따라 영향받는 모든 프로젝트 식별

4. 해당 프로젝트들만 실행

5.5 Generators (코드 생성기)

React 컴포넌트 생성

npx nx generate @nx/react:component Button --project=ui

라이브러리 생성

npx nx generate @nx/js:library shared-utils

커스텀 Generator 생성

npx nx generate @nx/plugin:generator my-generator --project=tools

5.6 Nx Cloud (원격 캐싱 + 분산 실행)

Nx Cloud 연결

npx nx connect

분산 실행 설정 (CI에서)

.github/workflows/ci.yml

name: CI

on: [push]

jobs:

main:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

with:

fetch-depth: 0

- uses: pnpm/action-setup@v2

- uses: actions/setup-node@v4

- run: pnpm install --frozen-lockfile

- uses: nrwl/nx-set-shas@v4

- run: npx nx affected -t lint test build

6. Turborepo 심층 분석

6.1 Turborepo 초기 설정

새 Turborepo 프로젝트 생성

npx create-turbo@latest

기존 모노레포에 Turborepo 추가

pnpm add -D turbo -w

6.2 turbo.json 설정

{

"$schema": "https://turbo.build/schema.json",

"globalDependencies": ["**/.env.*local"],

"globalEnv": ["NODE_ENV"],

"tasks": {

"build": {

"dependsOn": ["^build"],

"inputs": ["$TURBO_DEFAULT$", ".env*"],

"outputs": ["dist/**", ".next/**", "!.next/cache/**"],

"env": ["DATABASE_URL"]

},

"test": {

"dependsOn": ["build"],

"inputs": ["$TURBO_DEFAULT$"],

"outputs": ["coverage/**"]

},

"lint": {

"dependsOn": ["^build"],

"cache": true

},

"dev": {

"cache": false,

"persistent": true

},

"clean": {

"cache": false

}

}

}

6.3 Task Pipeline 동작 원리

turbo run build 실행 시:

1. 의존성 그래프 분석

utils (의존 없음) → ui (utils에 의존) → web (ui, utils에 의존)

2. 병렬 실행

[utils: build] ──완료──▶ [ui: build] ──완료──▶ [web: build]

[api: build] ──────────┘

(utils에만 의존)

3. 캐싱

- 입력 파일의 해시 계산

- 이전 빌드와 해시 동일하면 캐시에서 복원

- 빌드 시간: 5분 → 0.1초 (캐시 히트)

6.4 캐싱 메커니즘

로컬 캐시 위치

ls node_modules/.cache/turbo/

캐시 상태 확인

turbo run build --dry-run

캐시 무효화

turbo run build --force

원격 캐시 활성화

npx turbo login

npx turbo link

6.5 필터링과 스코핑

특정 패키지만 빌드

turbo run build --filter=@myorg/web

변경된 패키지만 빌드

turbo run build --filter=...[HEAD^1]

특정 패키지와 그 의존성만 빌드

turbo run build --filter=@myorg/web...

특정 디렉토리의 패키지만

turbo run build --filter=./apps/*

6.6 원격 캐싱 설정

Vercel Remote Cache (공식)

npx turbo login

npx turbo link

셀프호스팅 (ducktape/turborepo-remote-cache)

turbo.json에 추가:

{

"remoteCache": {

"signature": true

}

}

환경 변수 설정:

TURBO_TOKEN=your-token

TURBO_TEAM=your-team

TURBO_API=https://your-cache-server.com

7. 공유 라이브러리 전략

7.1 패키지 분류

packages/

├── ui/ # UI 컴포넌트 (Button, Modal, Form)

├── utils/ # 유틸리티 (날짜, 문자열, 검증)

├── types/ # 공유 TypeScript 타입

├── config/ # 공유 설정 (ESLint, TypeScript, Prettier)

├── hooks/ # 공유 React hooks

├── api-client/ # API 클라이언트 (타입 안전 fetch)

└── constants/ # 상수 (에러 코드, 라우트 경로)

7.2 Internal Package (내부 패키지) 패턴

빌드 없이 TypeScript 소스를 직접 참조하는 패턴:

{

"name": "@myorg/ui",

"private": true,

"main": "./src/index.ts",

"types": "./src/index.ts",

"exports": {

".": "./src/index.ts",

"./*": "./src/*.ts"

}

}

소비하는 앱의 `tsconfig.json`에서 path alias 설정:

{

"compilerOptions": {

"paths": {

"@myorg/ui": ["../../packages/ui/src"],

"@myorg/ui/*": ["../../packages/ui/src/*"]

}

}

}

7.3 빌드된 패키지 패턴

npm에 배포할 패키지는 별도 빌드 필요:

{

"name": "@myorg/utils",

"version": "1.0.0",

"main": "./dist/index.js",

"module": "./dist/index.mjs",

"types": "./dist/index.d.ts",

"exports": {

".": {

"import": "./dist/index.mjs",

"require": "./dist/index.js",

"types": "./dist/index.d.ts"

}

},

"scripts": {

"build": "tsup src/index.ts --format esm,cjs --dts"

}

}

8. 버전 관리와 Changesets

8.1 Changesets 소개

Changesets는 모노레포에서 버전 관리와 체인지로그 생성을 자동화하는 도구입니다.

설치

pnpm add -D @changesets/cli -w

초기화

pnpm changeset init

8.2 워크플로우

1. 변경사항 설명 추가

pnpm changeset

? 어떤 패키지가 변경되었나요? → @myorg/ui, @myorg/utils

? 변경 유형은? → minor (새 기능)

? 변경 설명은? → Added new Button variant

2. .changeset/ 디렉토리에 파일 생성됨

cat .changeset/brave-dogs-run.md

---

"@myorg/ui": minor

"@myorg/utils": patch

---

#

Added new Button variant

3. 버전 업데이트 (CI에서 실행)

pnpm changeset version

4. 배포

pnpm changeset publish

8.3 설정 (.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/hooks"]],

"access": "restricted",

"baseBranch": "main",

"updateInternalDependencies": "patch",

"ignore": ["@myorg/web", "@myorg/api"]

}

- **linked**: 함께 버전이 올라가는 패키지 그룹

- **fixed**: 항상 같은 버전을 유지하는 패키지 그룹

- **ignore**: 배포 대상에서 제외할 패키지 (앱 등)

8.4 Independent vs Fixed 버전 관리

| 전략 | 설명 | 예시 |

|------|------|------|

| Independent | 각 패키지 독립 버전 | ui@2.3.0, utils@1.5.0 |

| Fixed | 모든 패키지 동일 버전 | ui@3.0.0, utils@3.0.0 |

| Linked | 관련 패키지 연동 버전 | ui@2.3.0 바뀌면 hooks@2.3.0도 |

9. CI/CD 최적화

9.1 Affected 빌드 전략

변경된 파일에 영향받는 프로젝트만 빌드/테스트합니다.

.github/workflows/ci.yml

name: CI

on:

pull_request:

branches: [main]

jobs:

build:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

with:

fetch-depth: 0

- uses: pnpm/action-setup@v2

with:

version: 9

- uses: actions/setup-node@v4

with:

node-version: 20

cache: "pnpm"

- run: pnpm install --frozen-lockfile

Turborepo: 변경된 패키지만 빌드

- run: turbo run build test lint --filter=...[origin/main]

또는 Nx: 영향받는 프로젝트만

- run: npx nx affected -t build test lint

9.2 원격 캐싱으로 CI 시간 단축

캐시 없이:

lint (2분) + test (5분) + build (8분) = 15분

원격 캐시 히트:

lint (0.1초) + test (0.2초) + build (0.3초) = 0.6초

실제 변경된 패키지만:

lint (20초) + test (1분) + build (2분) = 3분 20초

9.3 병렬 실행 전략

매트릭스 전략으로 병렬 실행

jobs:

detect:

runs-on: ubuntu-latest

outputs:

packages: ${{ steps.filter.outputs.packages }}

steps:

- uses: actions/checkout@v4

- id: filter

run: echo "packages=$(turbo run build --filter=...[origin/main] --dry-run=json | jq -c '.packages')" >> $GITHUB_OUTPUT

build:

needs: detect

runs-on: ubuntu-latest

strategy:

matrix:

package: ${{ fromJson(needs.detect.outputs.packages) }}

steps:

- uses: actions/checkout@v4

- run: turbo run build --filter=${{ matrix.package }}

9.4 Docker 빌드 최적화

모노레포에서의 Docker 빌드

FROM node:20-slim AS base

RUN corepack enable

FROM base AS pruned

WORKDIR /app

COPY . .

turbo prune: 특정 앱과 의존성만 추출

RUN npx turbo prune @myorg/api --docker

FROM base AS installer

WORKDIR /app

의존성만 먼저 설치 (캐시 레이어)

COPY --from=pruned /app/out/json/ .

COPY --from=pruned /app/out/pnpm-lock.yaml ./pnpm-lock.yaml

RUN pnpm install --frozen-lockfile

소스 복사 및 빌드

COPY --from=pruned /app/out/full/ .

RUN pnpm turbo run build --filter=@myorg/api

FROM base AS runner

WORKDIR /app

COPY --from=installer /app/apps/api/dist ./dist

COPY --from=installer /app/node_modules ./node_modules

CMD ["node", "dist/main.js"]

10. CODEOWNERS와 팀 경계

10.1 CODEOWNERS 파일

.github/CODEOWNERS

전역 기본 소유자

* @org/platform-team

앱별 소유자

/apps/web/ @org/frontend-team

/apps/api/ @org/backend-team

/apps/mobile/ @org/mobile-team

패키지별 소유자

/packages/ui/ @org/design-system-team

/packages/utils/ @org/platform-team

/packages/auth/ @org/security-team

설정 파일

/tooling/ @org/dx-team

/.github/ @org/dx-team

/turbo.json @org/dx-team

10.2 팀 경계 설정 (Nx Module Boundaries)

// .eslintrc.json

{

"rules": {

"@nx/enforce-module-boundaries": [

"error",

{

"depConstraints": [

{

"sourceTag": "scope:web",

"onlyDependOnLibsWithTags": ["scope:shared", "scope:web"]

},

{

"sourceTag": "scope:api",

"onlyDependOnLibsWithTags": ["scope:shared", "scope:api"]

},

{

"sourceTag": "type:app",

"onlyDependOnLibsWithTags": ["type:lib", "type:util"]

},

{

"sourceTag": "type:lib",

"onlyDependOnLibsWithTags": ["type:lib", "type:util"]

}

]

}

]

}

}

10.3 PR 리뷰 자동 할당

.github/workflows/auto-assign.yml

name: Auto Assign Reviewers

on:

pull_request:

types: [opened]

jobs:

assign:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Auto assign based on changed files

uses: kentaro-m/auto-assign-action@v2

with:

configuration-path: .github/auto-assign.yml

11. 폴리레포에서 모노레포로 마이그레이션

11.1 단계별 마이그레이션

**1단계: 준비**

새 모노레포 저장소 생성

mkdir my-monorepo && cd my-monorepo

git init

pnpm init

**2단계: Git 히스토리를 보존하며 이동**

기존 저장소를 서브디렉토리로 이동 (히스토리 보존)

git remote add -f web-repo https://github.com/org/web-app.git

git merge web-repo/main --allow-unrelated-histories

git-filter-repo로 디렉토리 구조 변경

git filter-repo --to-subdirectory-filter apps/web

**3단계: 워크스페이스 설정**

pnpm workspace 설정

cat > pnpm-workspace.yaml << 'EOF'

packages:

- "apps/*"

- "packages/*"

EOF

루트 package.json 설정

(위 4.3절 참조)

**4단계: 공유 패키지 추출**

중복 코드를 공유 패키지로 추출

mkdir -p packages/shared-utils/src

공통 유틸 이동 및 패키지 설정

**5단계: CI/CD 업데이트**

Turborepo 또는 Nx 설정

pnpm add -D turbo -w

turbo.json 설정 (위 6.2절 참조)

11.2 점진적 마이그레이션 전략

한 번에 모든 저장소를 옮기지 마세요. 추천 순서:

1. 공유 라이브러리 먼저 이동

2. 가장 많이 의존하는 앱 이동

3. 나머지 앱 순차적 이동

4. 각 단계에서 CI/CD 검증

12. 일반적인 함정과 해결책

12.1 느린 CI

**문제**: 모든 패키지를 매번 빌드/테스트하여 CI가 30분 이상 소요

**해결**:

- Affected 명령 사용 (변경된 것만 빌드)

- 원격 캐싱 활성화

- 병렬 실행 설정

Before: 모든 패키지 빌드 (15분)

pnpm -r run build

After: 변경된 패키지만 (2분)

turbo run build --filter=...[origin/main]

12.2 의존성 지옥

**문제**: 패키지 A가 lodash@4를 쓰고 패키지 B가 lodash@3을 쓸 때

**해결**:

- pnpm의 strict mode 사용 (phantom dependency 방지)

- 공유 의존성은 루트에서 관리

- `syncpack`으로 버전 동기화

syncpack으로 의존성 버전 확인

npx syncpack list-mismatches

npx syncpack fix-mismatches

12.3 코드 소유권 불명확

**문제**: 누가 어떤 코드를 책임지는지 불명확

**해결**:

- CODEOWNERS 파일 설정 (10.1절 참조)

- Nx module boundaries로 의존성 제한

- 팀별 패키지 네임스페이스

12.4 초기 클론 시간

**문제**: 대규모 모노레포의 `git clone`이 10분 이상 소요

**해결**:

Shallow clone

git clone --depth 1 https://github.com/org/monorepo.git

Partial clone (blob 없이)

git clone --filter=blob:none https://github.com/org/monorepo.git

Sparse checkout (특정 디렉토리만)

git clone --sparse https://github.com/org/monorepo.git

cd monorepo

git sparse-checkout set apps/web packages/ui

12.5 빌드 순서 문제

**문제**: 패키지 A가 패키지 B에 의존하는데 B가 아직 빌드되지 않음

**해결**: Task pipeline의 `dependsOn` 설정

{

"tasks": {

"build": {

"dependsOn": ["^build"]

}

}

}

`^build`는 현재 패키지의 의존성들의 build를 먼저 실행하라는 의미입니다.

13. 면접 질문 모음 (10문제)

Q1. 모노레포와 폴리레포의 차이점과 각각의 장단점을 설명하라.

**모범 답변**: 모노레포는 여러 프로젝트를 단일 저장소에서 관리하여 코드 공유가 용이하고 원자적 변경이 가능하지만 CI 최적화가 필요합니다. 폴리레포는 각 프로젝트를 독립 저장소로 관리하여 독립성이 높지만 코드 공유가 어렵고 크로스 저장소 변경이 복잡합니다.

Q2. Nx와 Turborepo의 핵심 차이점은?

**모범 답변**: Nx는 프로젝트 그래프 시각화, 코드 생성기, 프레임워크 플러그인, 분산 실행 등 풍부한 기능을 제공하지만 학습 곡선이 높습니다. Turborepo는 캐싱과 태스크 파이프라인에 집중하여 설정이 간단하지만 코드 생성이나 시각화 기능은 없습니다.

Q3. Affected 명령의 동작 원리를 설명하라.

**모범 답변**: Git diff로 변경된 파일을 감지하고, 프로젝트 그래프에서 해당 파일이 속한 프로젝트와 그에 의존하는 모든 프로젝트를 찾아 해당 프로젝트들만 빌드/테스트합니다. 이를 통해 CI 시간을 90% 이상 줄일 수 있습니다.

Q4. 원격 캐싱이 CI 성능을 개선하는 원리는?

**모범 답변**: 빌드의 입력(소스 코드, 설정 등)을 해싱하여 동일한 입력이면 이전 빌드 결과를 클라우드 캐시에서 복원합니다. 동일한 코드를 여러 개발자가 빌드하거나 CI에서 반복 빌드할 때 빌드를 건너뛸 수 있습니다.

Q5. pnpm workspace의 `workspace:*` 프로토콜은 무엇인가?

**모범 답변**: `workspace:*`는 로컬 워크스페이스의 패키지를 직접 참조하는 프로토콜입니다. 심볼릭 링크로 연결되어 빌드 없이 소스 변경이 즉시 반영됩니다. npm에 배포할 때 pnpm이 자동으로 실제 버전 번호로 교체합니다.

Q6. Changesets의 워크플로우를 설명하라.

**모범 답변**: 개발자가 `pnpm changeset`으로 변경사항을 기술하면 .changeset/ 디렉토리에 마크다운 파일이 생성됩니다. PR 머지 후 CI에서 `changeset version`으로 패키지 버전을 업데이트하고, `changeset publish`로 npm에 배포합니다.

Q7. CODEOWNERS가 모노레포에서 중요한 이유는?

**모범 답변**: 모노레포에서는 여러 팀의 코드가 한 저장소에 있어 코드 소유권이 불명확해질 수 있습니다. CODEOWNERS 파일로 디렉토리별 담당 팀을 지정하면, PR에 자동으로 리뷰어가 할당되어 코드 품질과 책임 소재를 보장합니다.

Q8. 모노레포에서 Docker 빌드를 최적화하는 방법은?

**모범 답변**: Turborepo의 `turbo prune`으로 특정 앱과 그 의존성만 추출한 뒤 Docker에서 빌드합니다. 멀티스테이지 빌드에서 의존성 설치와 소스 빌드를 분리하여 Docker 레이어 캐싱을 활용합니다.

Q9. 폴리레포에서 모노레포로 마이그레이션할 때 주의점은?

**모범 답변**: Git 히스토리 보존이 중요합니다. `git-filter-repo`로 서브디렉토리 이동 시 히스토리를 유지할 수 있습니다. 한 번에 모든 저장소를 이전하지 말고 공유 라이브러리부터 점진적으로 이동하며, 각 단계에서 CI/CD를 검증해야 합니다.

Q10. 모노레포에서 Nx module boundaries의 역할은?

**모범 답변**: ESLint 규칙으로 패키지 간 의존성을 제한합니다. 프로젝트에 태그를 지정하고, 특정 태그의 프로젝트만 참조할 수 있게 규칙을 설정합니다. 예를 들어 frontend 앱이 backend 전용 패키지를 참조하는 것을 방지합니다.

14. 실전 퀴즈 (5문제)

**정답**: `^` 접두사는 현재 패키지의 **의존성(dependencies)** 들의 build 태스크를 먼저 실행하라는 의미입니다. 예를 들어 패키지 A가 패키지 B에 의존하면, A의 build 전에 B의 build가 먼저 완료됩니다. `^` 없이 `["build"]`만 쓰면 같은 패키지 내의 다른 태스크 의존성을 의미합니다.

**정답**: pnpm은 기본적으로 패키지를 격리된 `node_modules` 구조에 설치하여 phantom dependency(선언하지 않은 의존성 사용)를 방지합니다. `shamefully-hoist=false`는 이 엄격한 격리를 유지합니다. 호이스팅을 활성화하면 선언하지 않은 패키지에 접근할 수 있어, 나중에 배포 시 문제가 발생할 수 있습니다.

**정답**: **linked**는 그룹 내 패키지 중 하나의 버전이 올라가면 나머지도 같은 수준으로 올라갑니다 (예: 하나가 minor 올라가면 나머지도 minor). **fixed**는 그룹 내 모든 패키지가 항상 동일한 버전 번호를 유지합니다 (예: 모두 3.0.0에서 3.1.0으로).

**정답**: Partial clone으로 blob(파일 내용)을 초기에 다운로드하지 않고, 필요할 때만 가져옵니다. 대규모 모노레포에서 초기 클론 시간을 크게 줄일 수 있습니다. Git이 체크아웃하는 파일만 blob을 가져오므로, sparse checkout과 함께 사용하면 필요한 파일만 최소한으로 다운로드합니다.

**정답**: 소스 파일, 설정 파일, 환경 변수, 의존성의 빌드 결과 등 태스크의 모든 입력을 해싱하여 캐시 키를 생성합니다. 동일한 입력이면 동일한 해시가 나오므로, 이전에 빌드한 결과를 캐시에서 복원할 수 있습니다. Turborepo는 `turbo.json`의 `inputs`와 `env` 설정으로 캐시 키에 포함할 요소를 정의합니다.

15. 참고 자료

1. [Nx Documentation](https://nx.dev/getting-started/intro) — Nx 공식 문서

2. [Turborepo Documentation](https://turbo.build/repo/docs) — Turborepo 공식 문서

3. [Changesets Documentation](https://github.com/changesets/changesets) — Changesets 공식 문서

4. [pnpm Workspace](https://pnpm.io/workspaces) — pnpm 워크스페이스 공식 문서

5. [Google Monorepo Paper](https://research.google/pubs/pub45424/) — Why Google Stores Billions of Lines of Code in a Single Repository

6. [Lerna Documentation](https://lerna.js.org/) — Lerna 공식 문서

7. [Rush Documentation](https://rushjs.io/) — Rush 공식 문서 (Microsoft)

8. [Monorepo Explained](https://monorepo.tools/) — 모노레포 도구 비교 사이트

9. [Turborepo Caching](https://turbo.build/repo/docs/core-concepts/caching) — 캐싱 메커니즘 상세

10. [Nx Affected](https://nx.dev/concepts/affected) — Affected 명령 동작 원리

11. [Git Sparse Checkout](https://git-scm.com/docs/git-sparse-checkout) — 대규모 저장소 최적화

12. [CODEOWNERS Syntax](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) — GitHub CODEOWNERS 문법

현재 단락 (1/512)

Google은 20억 줄의 코드를 하나의 저장소에서 관리합니다. Meta, Microsoft, Uber, Airbnb도 모노레포를 사용합니다. 모노레포는 더 이상 실험적 전략이 아...

작성 글자: 0원문 글자: 15,587작성 단락: 0/512