Dev Notes
Web Vitals
Web Vitals
Dev Notes
DocsMonorepo 전략: Turborepo와 Nx
Web Vitals
Web Vitals
GitHub
Architecture46분

Monorepo 전략: Turborepo와 Nx

대규모 프론트엔드 프로젝트를 위한 모노레포 전략과 빌드 오케스트레이션 도구 비교

2026년 1월 30일
architecturemonorepoturboreponxpnpmworkspacesscalability

적용 환경: React, Vue, Angular 등 프레임워크 무관. npm/yarn/pnpm 워크스페이스 기반

들어가며

프론트엔드 팀이 성장하면서 자연스럽게 여러 애플리케이션을 운영하게 됩니다. 고객용 웹 앱, 관리자 대시보드, 모바일 웹뷰까지 — 서로 다른 제품이지만 공유해야 하는 코드가 점점 늘어납니다. 디자인 시스템의 Button 컴포넌트, API 클라이언트, 날짜 포맷팅 유틸리티처럼 동일한 로직이 여러 저장소에 복사되기 시작하면 문제가 눈에 보이기 시작합니다.

디자인 시스템 팀이 Button의 padding을 변경했다고 가정해 봅시다. 이 변경이 3개 앱에 반영되려면, 먼저 디자인 시스템 패키지의 버전을 올려 npm에 배포하고, 각 앱 저장소에서 새 버전으로 업데이트한 뒤, 각각 테스트하고 배포해야 합니다. 이 과정에서 하나의 앱이 업데이트를 놓치면 사용자는 페이지마다 다른 Button을 보게 됩니다. 단순한 padding 변경 하나에 이 정도 조율 비용이 든다면, 실질적인 기능 변경은 얼마나 복잡해질지 상상하기 어렵지 않습니다.

새 팀원이 합류했을 때 "공유 유틸리티는 어느 레포에 있나요?"라는 질문에 "ui-components 레포에 일부 있고, shared-utils 레포에도 있고, 각 앱 레포의 lib 폴더에도 일부 복사되어 있습니다"라고 답해야 하는 상황은 팀의 생산성을 갉아먹습니다.

모노레포는 이 "코드 공유와 변경 전파" 문제에 대한 구조적 해결책입니다. 하나의 저장소에서 여러 패키지를 관리하되, 각 패키지의 경계는 명확히 유지하는 방식입니다. Feature-Sliced Design이 하나의 패키지 내부를 어떻게 구조화할지 다루었다면, 모노레포는 여러 패키지를 어떻게 조직하고 관리할지에 대한 답을 제시합니다.

모노레포란

모노레포(Monorepo)는 여러 프로젝트의 코드를 하나의 저장소에서 관리하는 전략입니다. 흔히 모놀리스와 혼동되지만, 둘의 성격은 근본적으로 다릅니다. 모놀리스가 하나의 거대한 코드베이스를 단일 배포 단위로 묶는 구조라면, 모노레포는 독립적인 패키지들이 저장소만 공유할 뿐 각자의 빌드, 테스트, 배포 파이프라인을 유지할 수 있습니다. 따라서 멀티레포와 동일한 수준의 모듈성을 확보하면서도, 코드 공유와 변경 관리가 극적으로 단순해집니다.

모노레포가 제공하는 핵심 가치를 한마디로 요약하면 하나의 커밋으로 모든 것을 동기화할 수 있다는 것입니다. 공유 라이브러리를 수정하면서 그 라이브러리를 사용하는 모든 앱을 같은 커밋에서 업데이트할 수 있으므로, 버전 불일치나 호환성 문제가 원천적으로 차단됩니다. ESLint 설정, TypeScript 설정, CI 파이프라인도 하나의 저장소에서 통일할 수 있어 "이 레포에서는 되는데 저 레포에서는 안 되는" 상황을 방지합니다.

멀티레포모노레포모놀리스
코드 저장소패키지별 분리하나의 저장소하나의 저장소
패키지 경계명확 (npm 배포)명확 (워크스페이스)불명확
의존성 업데이트각 레포에서 개별 업데이트한 번에 업데이트해당 없음
CI/CD레포별 독립 파이프라인영향받는 패키지만 빌드전체 빌드
코드 공유npm 패키지 배포직접 참조직접 참조 (경계 없음)
팀 자율성높음중간낮음

모노레포 도구의 발전

JavaScript 생태계에서 모노레포 도구는 2015년 Lerna의 등장과 함께 본격적으로 발전하기 시작했습니다. 초기에는 여러 패키지의 버전 관리와 npm 배포 자동화가 주된 관심사였으나, 패키지 매니저 자체가 워크스페이스를 지원하면서 관심의 축이 빌드 최적화로 옮겨갔습니다.

2015-2017

Lerna

최초의 JS 모노레포 도구. 패키지 버저닝과 배포 자동화

2017-2018

Yarn Workspaces

패키지 매니저 수준의 워크스페이스 지원 시작

2020

pnpm Workspaces

효율적인 디스크 사용과 엄격한 의존성 격리

2021-현재

Turborepo / Nx

빌드 캐싱, 태스크 그래프, 병렬 실행 기반 오케스트레이션

2015-2017

Lerna

최초의 JS 모노레포 도구. 패키지 버저닝과 배포 자동화

2017-2018

Yarn Workspaces

패키지 매니저 수준의 워크스페이스 지원 시작

2020

pnpm Workspaces

효율적인 디스크 사용과 엄격한 의존성 격리

2021-현재

Turborepo / Nx

빌드 캐싱, 태스크 그래프, 병렬 실행 기반 오케스트레이션

오늘날 모노레포 관리는 두 개의 레이어로 나뉩니다. 하나는 패키지 매니저(npm, yarn, pnpm)가 담당하는 워크스페이스 프로토콜로, 패키지 간 심링크와 의존성 해석을 처리합니다. 다른 하나는 Turborepo나 Nx 같은 도구가 담당하는 빌드 오케스트레이션으로, 태스크 실행 순서와 캐싱, 병렬 처리를 최적화합니다. 두 레이어는 서로 독립적이기 때문에, 패키지 매니저와 빌드 도구를 자유롭게 조합할 수 있습니다.

워크스페이스: 모노레포의 기반

워크스페이스는 모노레포의 토대입니다. 패키지 매니저가 제공하는 워크스페이스 기능을 통해 하나의 저장소 안에서 여러 패키지를 인식하고, 패키지 간 참조를 심링크로 연결하기 때문에, 별도의 npm 배포 없이도 @my-org/ui 같은 내부 패키지를 마치 외부 라이브러리처럼 import할 수 있습니다.

표준적인 모노레포 구조는 apps/ 디렉토리에 배포 가능한 애플리케이션을, packages/ 디렉토리에 공유 라이브러리를 배치합니다.

my-monorepo
apps
web
package.json
next.config.js
src/
admin
package.json
vite.config.ts
src/
packages
ui
package.json
src/
shared
package.json
src/
tsconfig
package.json
base.json
nextjs.json
vite.json
package.json
pnpm-workspace.yaml
turbo.json

워크스페이스 설정은 패키지 매니저마다 약간 다르지만, 핵심은 어떤 디렉토리를 패키지로 인식할지 선언하는 것입니다.

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

워크스페이스가 설정되면 내부 패키지를 workspace: 프로토콜로 참조할 수 있습니다. 예를 들어 apps/web의 package.json에서 다음과 같이 선언하면, 패키지 매니저가 packages/ui 디렉토리를 심링크로 연결합니다.

JSON
// apps/web/package.json
{
  "name": "@my-org/web",
  "dependencies": {
    "@my-org/ui": "workspace:*",
    "@my-org/shared": "workspace:*",
    "next": "^15.0.0",
    "react": "^19.0.0"
  }
}

pnpm을 권장하는 이유

pnpm은 모노레포 환경에서 세 가지 실질적인 이점을 제공합니다. 첫째, content-addressable 저장소를 사용해 동일한 패키지를 디스크에 한 번만 저장하므로 node_modules 용량이 크게 줄어듭니다. 둘째, 엄격한 의존성 격리로 명시적으로 선언하지 않은 패키지에 접근할 수 없어서 "phantom dependency" 문제를 방지합니다. 셋째, pnpm-workspace.yaml로 워크스페이스 설정을 package.json과 분리할 수 있어 관심사가 명확해집니다.

공유 패키지 설계

모노레포의 실질적인 가치는 공유 패키지를 얼마나 잘 설계하느냐에 달려 있습니다. 패키지가 너무 크면 모놀리스와 다를 바 없고, 너무 잘게 쪼개면 관리 오버헤드가 늘어납니다. 실무에서 효과적인 패키지 분류는 보통 세 가지입니다.

UI 라이브러리(packages/ui)는 디자인 시스템 컴포넌트를 담으며, Button, Input, Modal 같은 기본 컴포넌트부터 앱 전반에 걸쳐 사용되는 레이아웃 컴포넌트까지 포함됩니다. 공유 유틸리티(packages/shared)에는 날짜 포맷팅, API 클라이언트, 공통 타입 정의처럼 UI와 무관한 로직이 들어갑니다. 그리고 공유 설정(packages/tsconfig, packages/eslint-config)을 통해 TypeScript, ESLint 등의 기반 설정을 저장소 전체에서 통일할 수 있습니다.

공유 패키지를 설계할 때 가장 신경 써야 할 부분은 package.json의 exports 필드입니다. 이 필드로 패키지의 진입점을 명시적으로 선언하면, 소비자가 내부 구현에 직접 접근하는 것을 원천적으로 차단할 수 있습니다.

JSON
// packages/ui/package.json
{
  "name": "@my-org/ui",
  "version": "0.0.0",
  "private": true,
  "exports": {
    ".": "./src/index.ts",
    "./styles.css": "./src/styles.css"
  },
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0",
    "react-dom": "^18.0.0 || ^19.0.0"
  }
}

패키지를 참조할 때는 반드시 워크스페이스 패키지명을 사용해야 합니다. 상대 경로로 다른 패키지의 내부 파일에 직접 접근하면 워크스페이스가 제공하는 캡슐화의 의미가 사라지기 때문입니다.

TypeScript
// ❌ 잘못된 예: 상대 경로로 다른 패키지 참조
import { Button } from '../../../packages/ui/src/components/Button';
TypeScript
// ✅ 올바른 예: 워크스페이스 패키지명으로 참조
import { Button } from '@my-org/ui';

빌드 오케스트레이션이 필요한 이유

워크스페이스만으로도 패키지 간 참조와 의존성 관리는 해결됩니다. 하지만 패키지가 10개를 넘어가기 시작하면 빌드 과정에서 새로운 문제에 부딪힙니다. pnpm -r run build로 전체를 빌드하면 모든 패키지가 무조건 다시 빌드될 뿐 아니라, 패키지 간 빌드 순서도 보장되지 않습니다. 예를 들어 @my-org/ui가 아직 빌드되지 않은 상태에서 @my-org/web이 먼저 빌드를 시작하면 실패하게 됩니다.

모노레포에서 패키지 간 의존 관계는 계층 구조를 형성합니다. 최하위의 설정 패키지는 빌드가 필요 없고, 중간층의 공유 라이브러리는 먼저 빌드되어야 하며, 최상위의 앱은 모든 의존성이 준비된 후에야 빌드할 수 있습니다.

apps/web(Next.js)
← 최종 빌드
apps/admin(Vite)
← 최종 빌드
packages/ui
← 먼저 빌드
packages/shared
packages/tsconfig(설정 전용)
← 빌드 불필요

빌드 오케스트레이터는 이 의존성 그래프를 분석하여 세 가지 핵심 문제를 해결합니다. 먼저, 태스크 의존성 그래프를 파악하여 올바른 순서로 빌드를 실행합니다. 다음으로, 캐싱을 통해 변경되지 않은 패키지의 빌드를 건너뜁니다. 마지막으로, 병렬 실행을 적용하여 독립적인 패키지를 동시에 빌드합니다. 그 결과, 10개 패키지 중 1개만 수정한 경우 나머지 9개는 캐시에서 즉시 복원되므로 전체 빌드 시간이 수 초 내로 줄어들 수 있습니다.

Turborepo

Turborepo는 Vercel이 관리하는 빌드 오케스트레이션 도구로, 최소한의 설정으로 최대한의 성능을 끌어내는 것을 목표로 합니다. 워크스페이스의 package.json을 분석하여 패키지 간 의존성 그래프를 자동으로 구축하기 때문에, 개발자는 turbo.json에 태스크 간의 관계만 정의하면 됩니다.

JSON
// turbo.json
{
  "$schema": "https://turborepo.dev/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tsconfig.json", "package.json"],
      "outputs": ["dist/**", ".next/**"]
    },
    "lint": {
      "dependsOn": ["^build"]
    },
    "test": {
      "dependsOn": ["^build"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

dependsOn의 ^ 접두사

"dependsOn": ["^build"]에서 ^는 "이 패키지가 의존하는 다른 워크스페이스 패키지들의 build를 먼저 실행하라"는 의미입니다. apps/web이 @my-org/ui에 의존한다면, web의 build 전에 ui의 build가 완료됩니다. ^ 없이 "dependsOn": ["lint"]로 작성하면 같은 패키지 내의 lint 태스크가 먼저 실행되어야 한다는 의미가 됩니다.

Turborepo의 핵심 CLI 명령어는 직관적입니다. --filter 플래그로 실행 범위를 좁히거나, 변경된 패키지만 선택적으로 빌드할 수 있습니다.

Bash
# 전체 워크스페이스 빌드
turbo build
 
# 특정 패키지와 그 의존성만 빌드
turbo build --filter=@my-org/web
 
# main 브랜치 이후 변경된 패키지만 빌드
turbo build --filter=...[main]
 
# 특정 앱의 개발 서버 실행
turbo dev --filter=@my-org/web

--filter는 CI 환경에서 특히 강력합니다. Pull Request에서 변경된 파일이 packages/ui 내부에만 있다면, turbo build --filter=@my-org/ui...로 해당 패키지와 그것에 의존하는 앱들만 빌드하여 CI 시간을 크게 단축할 수 있습니다.

Nx

Nx는 Nrwl이 개발한 빌드 시스템으로, Turborepo보다 한층 풍부한 기능 세트를 갖추고 있습니다. 프로젝트 그래프 시각화, 코드 제너레이터, 플러그인 시스템 등 대규모 조직에서 요구하는 도구를 폭넓게 제공하며, 특히 Angular 생태계와의 통합이 강력합니다.

Nx의 설정은 nx.json에서 전체 워크스페이스 수준의 기본값을 정의하고, 각 프로젝트의 세부 설정은 project.json에서 관리합니다.

JSON
// nx.json
{
  "$schema": "https://nx.dev/reference/nx-json",
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "lint": {
      "cache": true
    },
    "test": {
      "cache": true
    }
  },
  "namedInputs": {
    "default": ["{projectRoot}/**/*"],
    "production": [
      "default",
      "!{projectRoot}/**/*.spec.ts",
      "!{projectRoot}/**/*.test.ts"
    ]
  }
}

Nx가 다른 도구와 차별화되는 기능 중 하나는 nx graph 명령어입니다. 실행하면 브라우저에서 프로젝트 간 의존성 그래프를 인터랙티브하게 탐색할 수 있는데, 패키지가 수십 개를 넘어가는 대규모 모노레포에서 패키지 간 관계를 한눈에 파악하는 데 특히 유용합니다.

Bash
# 전체 프로젝트 빌드
npx nx run-many --target=build
 
# 특정 프로젝트만 빌드
npx nx build web
 
# main 브랜치 이후 영향받는 프로젝트만 빌드
npx nx affected --target=build --base=main
 
# 의존성 그래프 시각화 (브라우저에서 열림)
npx nx graph

Nx의 affected 명령어는 git diff를 분석하여 변경된 파일이 어떤 프로젝트에 속하는지 파악한 뒤, 해당 프로젝트와 그에 의존하는 모든 프로젝트를 자동으로 선택합니다. Turborepo의 --filter와 유사한 역할이지만, Nx는 이 분석을 자체적으로 수행하므로 별도의 필터 구문을 익힐 필요가 없다는 점이 다릅니다.

또한 Nx는 제너레이터(Generator) 기능을 내장하고 있어서, 새 라이브러리나 앱을 일관된 구조로 스캐폴딩할 수 있습니다. npx nx generate @nx/react:library shared-ui 같은 명령어로 표준화된 패키지 구조를 즉시 생성할 수 있으므로, 팀 내에서 "새 패키지를 어떻게 만들어야 하는지" 고민할 필요가 없어집니다.

Turborepo와 Nx 비교

두 도구 모두 빌드 캐싱, 태스크 오케스트레이션, 병렬 실행이라는 모노레포의 핵심 문제를 해결한다는 점에서는 동일하지만, 설계 철학과 적합한 규모에서 뚜렷한 차이를 보입니다.

기준TurborepoNx
설정 복잡도낮음 (turbo.json 하나)중간 (nx.json + project.json)
학습 곡선완만가파름
캐싱로컬 + Vercel Remote Cache로컬 + Nx Cloud
코드 생성turbo gen 내장제너레이터 내장 (플러그인 연동)
의존성 그래프 시각화turbo devtools / --graphnx graph 내장
CI 최적화--filter 기반affected 명령어 내장
프레임워크 지원프레임워크 무관React, Angular, Node.js 플러그인
적합한 규모소-중규모 (5-30 패키지)중-대규모 (20-200+ 패키지)
Vercel/Next.js 통합네이티브가능하지만 추가 설정 필요

Vercel과 Next.js 기반의 프로젝트라면 Turborepo가 자연스러운 선택입니다. turbo.json 하나로 설정이 완료되며, Vercel에 배포하면 리모트 캐시가 자동으로 활성화됩니다. 팀 규모가 작고 빠르게 도입해야 하는 상황에서도 학습 비용이 적기 때문에 적합합니다. 기존 워크스페이스 설정 위에 turbo.json만 추가하면 되므로, 점진적 도입이 수월합니다.

캐싱 전략

캐싱은 Turborepo와 Nx가 제공하는 가장 실질적인 가치입니다. 핵심 원리는 content-addressable hashing으로, 소스 파일, 환경 변수, 의존 패키지 버전 등 태스크의 입력값을 해싱하여 고유한 캐시 키를 생성합니다. 해당 키에 대응하는 빌드 결과물이 이미 존재하면 빌드 자체를 건너뛰고 캐시에서 복원하는 방식입니다.

캐시가 효과적으로 동작하려면 inputs와 outputs를 정확하게 설정해야 합니다. inputs를 지정하지 않으면 패키지 내 모든 파일 변경이 캐시를 무효화하므로, README 수정 하나에도 전체 빌드가 다시 실행됩니다.

JSON
// ❌ 잘못된 예: inputs 미지정으로 불필요한 캐시 무효화
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    }
  }
}
JSON
// ✅ 올바른 예: 빌드에 영향을 주는 파일만 inputs으로 지정
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tsconfig.json", "package.json"],
      "outputs": ["dist/**", ".next/**"],
      "env": ["NODE_ENV", "NEXT_PUBLIC_API_URL"]
    }
  }
}

로컬 캐시만으로도 개인 개발 환경에서는 충분히 효과적이지만, 진정한 가치는 리모트 캐시에서 드러납니다. 리모트 캐시를 활성화하면 한 개발자가 빌드한 결과를 다른 개발자와 CI 서버가 그대로 재사용할 수 있습니다. 예를 들어 동료가 이미 packages/ui를 빌드해 둔 상태라면, 내 로컬에서 같은 코드를 빌드할 때 리모트 캐시에서 결과물이 내려오면서 빌드를 건너뛰게 됩니다.

리모트 캐시의 실질적 효과

10개 이상의 패키지를 가진 모노레포에서 리모트 캐시를 적용하면, CI 빌드 시간이 평균 60-80% 단축됩니다. 특히 PR마다 전체 빌드를 돌리는 환경에서는 대부분의 패키지가 캐시 히트되므로, 실질적으로 변경된 패키지와 그 의존 패키지만 빌드하게 됩니다.

모노레포와 FSD의 결합

모노레포와 Feature-Sliced Design은 서로 다른 스케일의 문제를 다룹니다. FSD가 하나의 앱 패키지 내부에서 코드를 레이어(shared, entities, features, widgets, pages, app)로 구조화하는 반면, 모노레포는 여러 패키지 간의 관계를 조직합니다. 이 둘을 결합하면 패키지 내부의 코드 구조와 패키지 간 의존 관계가 모두 체계적으로 정리됩니다.

my-monorepo
apps
web
src
app/
pages/
widgets/
features/
entities/
shared/
admin
src
app/
pages/
features/
shared/
packages
ui/
shared/
tsconfig/

여기서 주의할 점은 FSD의 shared 레이어와 모노레포의 packages/shared가 이름은 같지만 역할이 다르다는 것입니다.

정보

FSD의 shared 레이어는 해당 앱 내부의 인프라 코드(UI 기본 컴포넌트, 유틸리티, API 클라이언트)이고, 모노레포의 packages/shared는 여러 앱이 공유하는 크로스앱 라이브러리입니다. 앱의 FSD shared 레이어에서 모노레포의 @my-org/shared와 @my-org/ui를 import하는 구조가 됩니다.

실질적인 활용 패턴은 다음과 같습니다. 앱 내부에서만 사용되는 컴포넌트, 로직, 타입은 해당 앱의 FSD 레이어에 두고, 2개 이상의 앱에서 공통으로 필요한 코드만 packages/의 공유 패키지로 추출합니다. 처음부터 모든 것을 공유 패키지로 만들기보다는, 앱 내부에서 시작하여 재사용 필요가 실제로 발생했을 때 패키지로 "졸업"시키는 편이 과도한 추상화를 방지하는 데 효과적입니다.

TypeScript
// apps/web/src/shared/api/client.ts
// FSD의 shared 레이어에서 모노레포의 공유 패키지를 활용
import { createApiClient } from '@my-org/shared';
import { API_BASE_URL } from '../config';
 
export const apiClient = createApiClient({ baseUrl: API_BASE_URL });

기존 프로젝트에 적용하기

멀티레포 구조에서 모노레포로의 전환을 반드시 한 번에 끝낼 필요는 없습니다. 오히려 점진적으로 진행하는 편이 리스크를 최소화하면서도, 각 단계에서 즉각적인 이점을 얻을 수 있어 현실적입니다.

워크스페이스 초기화

새 저장소를 만들고 루트에 package.json과 pnpm-workspace.yaml을 설정합니다. 가장 핵심적인 앱 하나를 apps/ 디렉토리로 이동하여 워크스페이스 환경이 정상적으로 동작하는지 검증합니다.

Bash
mkdir my-monorepo && cd my-monorepo
pnpm init
mkdir -p apps packages

공유 패키지 추출

여러 레포에 복사되어 있던 공통 코드를 packages/ 디렉토리의 독립 패키지로 추출합니다. UI 컴포넌트, 공통 유틸리티, TypeScript 설정부터 시작하는 것이 효과적입니다. 각 패키지에 package.json과 exports 필드를 설정하고, 앱에서 workspace:*로 참조하도록 전환합니다.

빌드 오케스트레이터 도입

패키지 수가 늘어나면 Turborepo나 Nx를 추가합니다. turbo.json 또는 nx.json을 작성하고, 빌드 캐싱이 정상적으로 동작하는지 확인합니다. 이 단계에서 CI 파이프라인이 turbo build 또는 nx affected --target=build를 사용하도록 전환합니다.

Bash
# Turborepo 도입
pnpm add -D turbo -w
 
# 또는 Nx 도입
pnpm add -D nx -w

나머지 앱 이전 및 CI/CD 통합

나머지 앱들을 하나씩 apps/ 디렉토리로 이전합니다. 각 앱을 이전할 때마다 공유 패키지로 추출할 수 있는 중복 코드가 있는지 확인합니다. CI/CD 파이프라인에 리모트 캐시를 활성화하고, PR 단위로 영향받는 패키지만 빌드·테스트하도록 최적화합니다.

마이그레이션 시 주의사항

기존 저장소의 git 히스토리를 보존하려면 git subtree 또는 git filter-repo를 사용하는 것이 좋습니다. 단순히 파일을 복사하면 커밋 히스토리가 사라집니다. 또한, 모든 앱을 한 번에 옮기기보다 하나씩 점진적으로 이전하는 편이 안전합니다. 각 이전 후 CI가 정상 동작하는지 반드시 확인한 뒤 다음 앱을 이전하는 것을 권장합니다.

정리

모노레포는 코드 공유, 의존성 관리, 빌드 최적화라는 세 가지 과제를 하나의 저장소 안에서 해결하는 실용적인 전략입니다.

핵심 정리

  1. 모노레포는 코드 공유와 일관성의 문제를 해결합니다. 여러 앱이 동일한 UI 라이브러리, 설정, 유틸리티를 하나의 저장소에서 관리하면 버전 불일치와 중복 코드가 사라집니다.
  2. 워크스페이스는 기반, 빌드 오케스트레이터는 성능입니다. pnpm workspaces로 패키지 간 연결을 관리하고, Turborepo나 Nx로 빌드 캐싱과 병렬 실행을 최적화합니다.
  3. 도구 선택은 팀 규모와 생태계에 따라 결정합니다. Vercel/Next.js 기반이라면 Turborepo, 대규모 조직이나 Angular 프로젝트라면 Nx가 적합합니다. 소규모라면 워크스페이스만으로도 충분합니다.
  4. FSD와 결합하면 전체 구조가 완성됩니다. FSD는 앱 내부의 코드를 비즈니스 도메인 중심으로 조직하고, 모노레포는 앱 간 공유와 의존성을 체계화합니다.

참고 자료

  • Turborepo 공식 문서
  • Nx 공식 문서
  • pnpm Workspaces
  • Monorepo Explained
  • Feature-Sliced Design 공식 문서
Written by

Mirunamu (Park Geonwoo)

Software Developer

관련 글 더보기

Architecture

Feature Sliced Design 아키텍처 가이드

비즈니스 도메인 중심의 프론트엔드 프로젝트 구조화 방법론

읽기
Architecture

함수형 프로그래밍?

Data, Calculation, Action — 코드를 읽는 함수형 프레임워크

읽기
이전 글Feature Sliced Design 아키텍처 가이드
다음 글함수형 프로그래밍?
목차
  • 들어가며
  • 모노레포란
  • 모노레포 도구의 발전
  • 워크스페이스: 모노레포의 기반
  • 공유 패키지 설계
  • 빌드 오케스트레이션이 필요한 이유
  • Turborepo
  • Nx
  • Turborepo와 Nx 비교
  • 캐싱 전략
  • 모노레포와 FSD의 결합
  • 기존 프로젝트에 적용하기
  • 정리
  • 참고 자료