요약
이 글은 모노레포와 멀티레포의 구조적 차이와 선택 기준을 다룹니다. pnpm workspace, Turborepo, Nx 등 주요 모노레포 도구의 특징을 비교하며, 실제 프로젝트 도입 사례를 통해 장단점을 분석합니다. 또한 초기 설정 자동화를 위한 CLI 개발 경험을 소개하며, 프로젝트 환경에 적합한 코드 관리 방식을 선택하는 데 유용한 인사이트를 제공합니다.
1. 시작하며
안녕하세요. 오픈소스프로젝트팀 이분도, 프레임워크기술팀 박서연, 문서뷰어개발팀 공소나입니다.
최근 프론트엔드 생태계에서 ‘모노레포’는 더 이상 낯선 개념이 아닙니다. 많은 회사들이 복잡한 프로젝트를 효율적으로 관리하고, 개발 생산성을 높이기 위한 전략으로 모노레포를 채택하고 있습니다. 하지만 모노레포가 모든 상황에 맞는 정답은 아닙니다.
이번 포스트에서는 3명의 개발자가 각 팀에서 프로젝트에 모노레포를 도입하고, 운영하며 겪었던 실제 경험을 공유하고자 합니다.
2. 모노레포란? : 멀티레포 vs 모노레포
2-1. 코드 관리 방식
개발을 하다 보면 하나의 프로젝트가 점점 커지고, 그에 따라 프로젝트도 분리되고 팀도 나뉘게 됩니다.
이때 코드를 어떻게 저장하고 관리할 것인가는 조직 전체의 생산성과 협업 방식에 큰 영향을 미치게 됩니다.
코드 관리 방식은 크게 멀티레포(Multirepo)와 모노레포(Monorepo)로 나뉩니다.

2-2. 모노레포 vs 멀티레포, 언제 어떤 방식을 쓸 것인가?
구분 | 모노레포 (Monorepo) | 멀티레포 (Multirepo) |
---|---|---|
핵심 가치 | • 효율 극대화 • 공유, 일관성 추구 | • 안정성 극대화 • 분리, 독립성 추구 |
주요 특징 | • 중앙 집중형 • 공유 코드 • 공유 규칙 • 동기화/버전 통합 관리 | • 분산형 • 독립 코드 • 독립 설정 • 저장소 간 동기화/버전 관리 부담 |
권한/거버넌스 | • 조직 전체에 접근 권한 부여 • 코드리뷰/정책을 일괄적 적용 | • 저장소별로 권한 분리 • 팀별로 릴리즈/정책 독립 운영 • 조직 전반의 표준화/통합 관리 어려움 |
그럼 언제 어떤 방식을 선택해야 할까요?
모노레포는 중앙 집중형으로 개발 규칙, 코드, 버전 등을 중앙에서 관리하고 공유하여 효율성을 극대화합니다. 이러한 구조 덕분에 개발팀은 빠른 속도와 높은 집중도로 비즈니스 가치에 더욱 전념할 수 있습니다.
반면 멀티레포의 경우, 프로젝트를 작은 단위로 격리하고 권한을 세분화하여 관리함으로써, 부분적인 실패가 전체로 확산되지 않도록 합니다. 특히 금융, 인프라, 공공 서비스와 같이 안정적인 운영이 비즈니스 가치 및 신뢰와 직결되는 프로젝트에서 적합합니다.
2-3. 이론보다 현실, 상황에 맞는 선택이 우선!
하지만 코드를 어떻게 관리하는 것이 유리한지는, 상황에 따라 크게 달라집니다.
이론보다는 실제 프로젝트가 처한 실무 환경이나 확장성을 고려해 모노레포나 멀티레포 중 어떤 구조가 더 적합할지 판단하는 것이 좋습니다. 또한, 꼭 둘 중 하나만 고집하기보다는 필요에 따라 두 구조의 중간 지점을 유연하게 조합하는 것도 충분히 효과적인 전략이 될 수 있습니다.
이제 스터디를 하며 각자 팀에서 모노레포를 도입해 본 사례를 중심으로, 모노레포와 멀티레포를 선택하게 된 배경과 그 과정에서의 경험을 자세히 다뤄보겠습니다.
3. 모노레포 도구 비교: pnpm workspace, Nx, Turborepo
도입 사례로 들어가기 전, 이해를 돕기 위해 모노레포 도구에 대해 먼저 살펴보겠습니다.
모노레포는 여러 프로젝트를 하나의 저장소에서 관리하며 코드 재사용성을 높여줍니다. 그러나 규모가 커질수록 빌드, 테스트, 의존성 관리의 복잡성이 증가하게 됩니다. 이러한 문제를 해결하기 위해 pnpm workspace, Nx, Turborepo와 같은 다양한 모노레포 프레임워크들이 등장했습니다. 각각의 특징과 적합한 사례를 비교하며 살펴보겠습니다.
3-1. pnpm workspace
pnpm workspace는 효율적인 의존성 관리에 초점을 맞추고 있습니다.
의존성을 한 번만 설치하고 여러 프로젝트가 공유하기 때문에 디스크 공간을 절약하고 설치 속도가 빠르다는 장점이 있습니다. 또한 pnpm-workspace.yaml
파일 하나로 간단하게 설정할 수 있어, 도구 학습 부담도 적습니다.
- 패키지 설치 속도가 중요한 프로젝트
- 빌드보다는 기본적인 의존성 관리가 핵심인 중소규모 모노레포
- 새로운 도구 학습 부담을 줄이고 싶은 팀
3-2. Turborepo
Turborepo는 복잡한 기능보다는 빌드 성능 최적화라는 단일 목표에 충실합니다.
지능적인 빌드 캐싱을 제공해 이전에 빌드된 결과를 재사용하고, 태스크(작업) 간의 의존성을 자동으로 분석해 가능한 모든 작업을 최대 병렬로 실행합니다. 특히, 원격 캐시 기능을 활용하면 CI/CD 환경에서 팀원 간 또는 빌드 간 캐시를 공유하여 빌드 시간을 더욱 단축할 수 있습니다.
- 빌드 속도 최적화가 핵심 목표인 프로젝트
- 복잡한 설정보다는 간결하고 빠른 시작을 선호하는 팀
3-3. Nx
Nx는 빌드 속도 최적화를 넘어 엔터프라이즈급 모노레포 관리 프레임워크를 지향합니다.
구글의 Angular 팀 출신들이 개발한 도구로, 지금은 React, Vue, Node.js 등 다양한 기술 스택을 지원합니다. Nx의 핵심은 지능형 캐싱과 병렬 빌드를 통한 강력한 성능 최적화, 그리고 시각화된 의존성 그래프입니다. 이를 통해 프로젝트 간 복잡한 의존 관계를 한눈에 파악하고, 일관된 개발 환경을 구축하는 데 도움을 줍니다.
- 대규모의 복잡한 모노레포 환경을 구축하고 관리해야 하는 엔터프라이즈급 프로젝트
- 일관된 코드베이스와 개발 워크플로우를 표준화하고 싶은 팀
- 풍부한 기능과 강력한 관리 도구를 원하는 경우
3-4. 모노레포 프레임워크 비교
3가지 프레임워크를 다시 한번 표로 정리해보면 다음과 같습니다.
특징 | pnpm workspace | Turborepo | Nx |
---|---|---|---|
핵심 가치 | • 간단한 의존성 관리 | • 대규모 프로젝트에서 빠른 빌드 속도 확보 | • 올인원 모노레포 플랫폼 |
주요 특징 | • 디스크 절약 • 빠른 설치 • 설정 최소화 | • 빌드/캐시 최적화 • 파이프라인 실행 • 원격 캐시 지원 • 빌드 단계 병렬/직렬 제어 | • 의존성 그래프 시각화 • 플러그인 생태계 • 테스트/빌드/배포 통합 • 다른 기술 스택 통합 지원 |
학습 곡선 | • 낮음 | • 중간 | • 높음 |
4. 모노레포 도입기 – 1 : [Project A] Turborepo 도입기
4-1. Turborepo 도입 배경
Project A는 기존에 pnpm workspace를 활용해 모노레포 구조로 관리해왔습니다.

이를 통해 패키지를 빠르게 설치하고 패키지 간 의존성을 효율적으로 관리할 수 있었지만, 프로젝트 규모가 커질수록 몇 가지 한계가 드러나기 시작했습니다.
코드 변경이 없어도 모든 앱과 패키지를 매번 처음부터 빌드해야 했고, 실제로는 App 1
만 집중 개발하고 있음에도 불필요하게 다른 앱까지 함께 재빌드되는 상황이 반복되었습니다. 또한 새로운 앱이나 패키지를 추가할 때마다 환경 설정을 수동으로 반복해야 하는 점도 확장성 측면에서 부담이 되었습니다.
이러한 문제를 해결하고 모노레포의 효율을 좀 더 극대화하기 위해 저희 팀에서는 Turborepo를 도입했습니다. 처음에는 Nx를 먼저 검토하고 일부 적용해보았지만, 설정 복잡도가 높고 러닝 커브가 크다는 점에서 부담이 있었습니다. 반면 Turborepo는 비교적 단순한 설정만으로도 원하는 캐싱과 병렬 빌드 기능을 빠르게 적용할 수 있어 최종적으로 Turborepo를 선택하게 되었습니다.
4-2. turbo.json 설정
turbo.json
은 Turborepo의 핵심 설정 파일로, 모노레포 내의 작업을 정의하고 실행 규칙을 관리합니다. 이 파일을 통해 빌드, 린트, 타입 검사 등 다양한 작업(task)을 선언하고, 각 작업의 의존성, 입력 및 출력 값을 명시하여 효율적인 빌드 환경을 구축할 수 있습니다.
모노레포에서 설정한 turbo.json 다음과 같습니다.
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["pnpm-lock.yaml"],
"tasks": {
"build": {
"dependsOn": [
"lint",
"check-types",
"@app3#build",
"@app2#build",
"@app1#build",
"@app4#build"
],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"check-types": {
"dependsOn": ["^check-types"]
},
"@app3#build": {
"outputs": ["../../dist/**"]
},
"@app2#build": {
"dependsOn": ["@app3#build"],
"outputs": ["../../dist/app2/**"]
},
"@app1#build": {
"dependsOn": ["@app3#build"],
"outputs": ["../../dist/app1/**"]
},
"@app4#build": {
"dependsOn": ["@app3#build"],
"outputs": ["../../dist/app4/**"]
}
}
}
tasks
tasks는 모노레포 내에서 실행할 모든 작업을 정의하는 객체입니다. 여기서는 build
, lint
, check-types
와 같은 공통 작업과 함께, @app1#build
와 같이 특정 워크스페이스(@app1
)에 특화된 빌드 작업들을 명시했습니다. @app1#build
는 해당 패키지의 package.json
에 정의된 build
스크립트를 실행하라는 의미입니다.
dependsOn
dependsOn은 특정 작업을 실행하기 전에 반드시 먼저 완료되어야 하는 작업들을 지정합니다. 예를 들어, build
작업은 lint
, check-types
, 그리고 각 애플리케이션의 빌드 작업이 먼저 성공적으로 끝나야 실행됩니다. 이 작업들은 병렬로 실행되며, 하나라도 실패하면 상위 작업인 build
는 실행되지 않습니다.
특히 ^
심볼은 Turborepo의 강력한 기능 중 하나입니다. dependsOn: ["^lint"]
와 같이 사용하면, Turbo가 패키지 간의 의존성 그래프를 자동으로 파악해 해당 패키지가 의존하는 모든 패키지의 lint
작업을 먼저 실행합니다.
inputs
inputs는 캐싱을 결정하는 기준 파일들입니다. $TURBO_DEFAULT$
는 Turborepo가 자동으로 캐싱에 포함하는 기본 파일들을 의미합니다.
outputs
outputs는 빌드 결과물이 저장되는 경로를 지정하여, 해당 경로의 파일을 캐시로 저장하거나 복원하는 데 사용됩니다.
4-4. 도입 후 빌드 성능 비교
🔷 기존 build.sh 소요 시간
Turborepo를 도입하기 전에는 build.sh
스크립트를 사용하여 각 애플리케이션을 순차적으로 빌드하고 병합했습니다. 이 스크립트의 실행 시간을 측정한 결과, 전체 빌드에 약 24초가 소요되었습니다.
sh .build.sh --mode dev 36.91s user 7.89s system 185% cpu 24.217 total
🔷 Turborepo 도입 후 빌드 소요 시간
Turborepo는 병렬 빌드와 캐싱 기능을 활용하여 빌드 시간을 획기적으로 단축했습니다.
- 최초 빌드: 캐시가 전혀 없는 상태에서 처음 빌드를 실행했을 때, 약 14초가 소요되었습니다.

- 전체 캐시 활용 시: 모든 작업이 캐시되어 있는 상태에서 다시 빌드를 실행하면, 모든 결과를 캐시에서 가져오므로 빌드 시간이 0.3초로 단축됩니다. 이는 코드 변경이 없는 상태에서 빠른 CI/CD 확인이나 개발 환경 설정에 큰 장점을 제공합니다.

- 부분 변경 후 재빌드 시:
app1
패키지의 코드만 변경하고 재빌드했을 때, Turborepo는 변경 사항이 없는 패키지는 캐시를 활용하고app1
만 재빌드합니다. 이 경우, 빌드 시간은 약 7초로 단축되었습니다.


4-5. 최종 결과 및 도입 효과
🔷 개발자 생산성 향상
가장 큰 성과는 로컬 개발 환경에서의 생산성 향상이었습니다. 코드를 수정하고 결과를 확인하는 빌드 시간이 평균 36초에서 7초로 크게 단축되었습니다. Turborepo의 캐싱 덕분에 변경된 파일만 재빌드하고, 변경되지 않은 부분은 즉시 완료되면서 개발자들은 훨씬 빠른 피드백 루프를 경험할 수 있었습니다.
🔷 CI/CD 파이프라인 최적화
Jenkins 빌드에서 기존 스크립트처럼 순차적으로 빌드하는 대신, Turborepo가 병렬로 작업을 실행하면서 전체 빌드 시간이 크게 줄었습니다. 이 덕분에 CI/CD 파이프라인의 속도가 눈에 띄게 향상되었습니다.
하지만 Jenkins의 특성 상 빌드할 때마다 새로운 환경을 생성하는 경우가 많아, 로컬 캐시를 재활용할 수 없다는 한계가 있었습니다. 이로 인해 매번 cache miss
상태로 빌드가 시작되면서, Turborepo의 가장 강력한 기능인 캐싱의 진정한 이점을 CI 환경에서는 온전히 활용하지 못했습니다.
이러한 상황은 Turborepo의 원격 캐싱 기능을 도입해야 할 필요성을 느끼게 해주었습니다. 원격 캐시를 사용하면, Jenkins 빌드 환경이 매번 초기화되더라도 이전에 빌드된 결과를 원격 저장소에서 불러와 재사용할 수 있습니다. 이는 향후 저희가 CI/CD 파이프라인을 더욱 최적화하기 위해 검토하고 있는 과제 중 하나입니다.
5. 모노레포 도입기 – 2 : [Project B] 모노레포, 그리고 다시 멀티레포
다음으로 Project B에 모노레포 도입을 시도했으나, 프로젝트 상황에 더 적합한 레포 관리 방식을 선택한 경험을 공유하고자 합니다.
5-1. 프로젝트 기존 구조
Project B의 기존 폴더 구조는 다음과 같습니다.
└── Root
└── App A (공통 라이브러리)
└── App B
└── App C
└── App D
└── App E
└── App F
└── App G
각 앱은 viewer
와 editor
폴더로 구성되어 있으며, 각각 독립적으로 빌드될 수 있도록 설계되어 있습니다.
또한, 심볼릭 링크를 사용하여 App A의 package.json, node_modules 등을 각 앱에 연결하여 사용합니다.
[VSCode 창 1] App A + App B workspace
├─ App A (공통 라이브러리)
└─ App B
[VSCode 창 2] App A + App C workspace
├─ App A (공통 라이브러리)
└─ App C
[VSCode 창 3] App A + App D workspace
├─ App A (공통 라이브러리)
└─ App D
[VSCode 창 4~6] App A + … workspace
이렇게 연결된 앱들은 workspace를 통해 VS Code 창을 켜서 개발하며, 여러 앱을 동시에 사용할 경우 각 앱마다 VS Code 창을 열고 전환하며 작업해야 했습니다.
5-2. 모노레포 도입
이 상황에서 여러 개의 앱을 공통으로 관리하는 팀의 입장에서는, 하나의 수정 사항이 발생할 때마다 각 앱을 일일이 열어 수정해야 했습니다. 이 방식은 너무 많은 창이 열리게 되어 번거롭고 작업 중 혼란을 유발한다는 단점이 있었습니다. 이러한 비효율을 개선하고, 여러 앱을 한 곳에서 통합적으로 관리할 수 있도록 모노레포 구조의 도입을 검토하게 되었습니다.
🔷 구조 설계
모노레포는 하나의 레포지토리 내에서 여러 프로젝트가 같은 브랜치를 공유한다는 특성이 있습니다. 하지만 저희가 사용하는 App A
는 별도의 브랜치를 유지해야 했기 때문에, 아래와 같은 방식으로 구조를 설계했습니다.
- 각 앱은
projects
폴더 하위에 위치 App A
는 별도의Git submodule
로 분리하여 연결- 패키지 관리와 빌드 도구로는
pnpm
과nx
를 함께 사용 - 4개의 앱(B~E)을 우선적으로 검증
이렇게 하여 공통 코드와 개별 앱을 함께 관리할 수 있는 기반을 마련했습니다.

🔷 문제점
하지만 도입 이후 예상치 못한 문제들도 나타났습니다.
가장 큰 이슈는 코드 권한 관리였습니다. 저희는 보안 정책 상 프로젝트별로 최소한의 인원에게만 코드 접근 권한을 부여하고 있습니다.
그러나 모노레포 구조에서는 하나의 저장소에 모든 앱이 함께 존재하기 때문에, 모든 팀이 모든 앱의 코드에 접근할 수 있게 되는 문제가 발생했습니다.
또한, 단일 앱만 사용하는 팀의 경우에도 전체 코드를 내려받아야 했기 때문에, 오히려 레포가 무겁게 느껴지는 부작용도 있었습니다.
5-3. 모노레포 개선 – 모노레포 + 멀티레포
앞서 언급한 문제들을 해결하기 위해, 새로운 구조를 고민하게 되었습니다.
하나의 앱만 다루는 팀은 해당 앱의 코드만 갖고 있도록 하고, 반대로 여러 앱을 관리해야 하는 팀은 복잡하지 않게 통합적으로 접근할 수 있는 구조를 목표로 했습니다.
이에 따라, 물리적으로는 각 앱을 개별 레포(멀티레포)로 분리했지만, 루트에서는 pnpm
과 nx
를 통해 논리적으로 모노레포처럼 동작하도록 구성한 하이브리드 구조로 재설계하였습니다.
🔷 구조 변경
이전에는 App A
만 서브모듈로 분리하고, 각 앱은 하나의 레포에서 함께 관리되고 있었습니다. 하지만 이 방식은 권한 문제와 불필요한 코드 접근 등의 한계를 가지고 있었기에, 모든 앱을 서브모듈로 분리하는 방향으로 구조를 변경하였습니다.
- 모든 앱을 각각의 Git 레포지토리로 분리
- 이를
projects
폴더 내에 서브모듈 형태로 구성 - 공통 프레임워크(
App A
) 역시 기존대로 서브모듈로 유지
이렇게 구성함으로써, 필요한 앱만 클론하거나 관리할 수 있게 되었고, 전체 구조는 유지하면서도 유연한 권한 관리가 가능해졌습니다.

🔷 하위 앱 구조 및 빌드 구성
각 앱은 내부적으로 viewer
와 editor
로 나뉘어 있어, 이를 각각 빌드할 수 있도록 nx
에서 실행 가능한 구조로 설정하였습니다. 이 구조는 모노레포의 일관성과 관리 편의성을 유지하면서도, 멀티레포의 유연한 권한 설정과 가벼운 코드베이스 유지라는 장점을 함께 취할 수 있었습니다.
5-4. 다시 멀티레포로
하지만 앞서 모노레포와 멀티레포를 혼합한 구조를 시도해 본 결과, 현재 구조에서는 로컬 빌드 속도에 큰 차이가 없었기 때문에 굳이 모노레포를 고수할 필요가 없다는 생각이 들었습니다.
또한, 각 팀이 사용하는 앱이 모두 다르다 보니 팀별로 서로 다른 형태의 모노레포를 구성해야 했고, 팀 단위로 작업 환경을 맞춰야 하는 번거로움도 발생했습니다.
결국, 모든 앱이 개별 레포지토리로 관리되고 있다면
“루트 폴더에서 각 앱만 선택적으로 실행하는 방식이 더 간단하고 유연하지 않을까?”라는 생각이 들었고,
이에 따라 다시 멀티레포 방식으로 구조를 전환해 보았습니다.
🔷 새롭게 구성한 구조
폴더 구조는 기존과 동일하게 유지했습니다. 다만, 기존의 불편함을 개선하기 위해 사용 방식을 일부 변경했습니다.
기존의 Root(가칭)
라는 최상위 폴더 안에 각 앱을 개별적으로 git clone
하는 구조를 그대로 유지하면서, 각 앱별로 workspace를 열지 않고, Root
폴더에서 VS Code를 열도록 했습니다. 그리고 사용성을 높이기 위해 .vscode
설정을 추가했습니다.

새로 추가한 .vscode/tasks.json
에는 각 폴더에 바로 접근할 수 있는 설정과, 앱별 viewer
와 editor
를 한눈에 확인할 수 있도록 터미널 구조를 자동 생성하는 코드를 포함했습니다.
/*.vscode/tasks.json */
{
... //각 터미널 세팅 내용 생략
{ // 폴더를 열 때 위 작업들을 모두 실행시키는 메인 작업
"label": "Startup Terminals",
"dependsOn": [
"App A",
"App B | editor",
"App B | terminal",
...
"App F | editor",
"App G | editor"
],
"runOptions": {
"runOn": "folderOpen"
}
}
}
덕분에 VS Code를 시작하면 터미널이 자동으로 세팅되어, 별도의 추가 작업 없이 바로 개발을 시작할 수 있습니다.

App A
와의 심볼릭 링크가 연결되어 있으면 원하는 앱의 터미널에서 바로 npm run start
실행이 가능하였고, 코드 참조나 정의로 이동이 제대로 동작하지 않을까 걱정했지만, 다행히 큰 오류 없이 VS Code 내에서 모든 앱을 한 번에 탐색 가능하여 편리해졌습니다.
하지만 여러 레포를 하나의 폴더에 두었더니 VS Code가 브레이크포인트를 인식되지 않는 문제가 발견되었습니다. 이를 해결하기 위해 .vscode
에 launch.json
파일을 추가하여 디버깅 환경을 세팅해보았습니다.
{
"configurations": [
{
"name": "Debug App B",
"request": "launch",
"type": "chrome",
"url": "http://localhost:4000",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"http://localhost:4000/tester/editor/src/*": "${workspaceFolder}/AppB/editor/src/*",
"http://localhost:4000/tester/editor/@fs/*/AppA/*": "${workspaceFolder}/AppA/src/*"
},
"sourceMaps": true,
}, ...
]
}
그 결과 원하는 디버그 구성을 선택해 두면, F5키로 기존처럼 실행할 수 있게 되었습니다.

이제 사용자가 .vscode 폴더만 복사/붙여넣기 하면 바로 모든 세팅이 끝날 수 있게 되었습니다.
🔷 새로운 구조의 장점
이전에는 각 앱별로 별도의 VS Code 워크스페이스를 열고 창을 이동하며 개발해야 했습니다. 또한, 코드 참조를 한눈에 확인할 수 없어서, 자신이 수정한 코드와 연결된 부분에서 오류가 발생했는지 확인하려면 여러 창을 돌아다니며 추가 검증을 해야 했습니다.
이를 개선하기 위해, 모노레포 적용과 같은 큰 구조 변경 없이 최상위 폴더 기반 개발 환경으로 전환하고 .vscode
폴더만 추가하는 간단한 방법을 적용했습니다. 그 결과, 모든 참조를 한눈에 확인할 수 있게 되었고, 개별 창을 켜 두는 번거로움도 사라졌습니다. 터미널도 자동으로 세팅되어, 별도의 추가 작업 없이 각 앱을 바로 빌드할 수 있게 되었습니다. 덕분에 환경 세팅 시간이 단축되고, 오류 발생 가능성도 줄어들었습니다.
장점 | 추가 설명 |
---|---|
앱별 클론 가능 | 사용자가 필요한 앱만 clone 하여 사용 가능 |
불필요한 설치 없음 | pnpm, nx 등 별도의 추가 설치 없이 기존 세팅으로 바로 진행 가능 |
간편한 실행 | 추가적인 설정 없이 VS Code만 열면 바로 실행 가능 |
권한 관리 용이 | 개별 앱 단위로 clone하므로 권한 문제도 자연스럽게 해결 |
코드 탐색 편의성 | 전체 앱의 코드 참조를 한 곳에서 가능 |
팀 내에서도 각자 clone 작업을 별도로 해야 하므로 처음 세팅할 경우,
팀 단위로 효율적인 협업 환경을 위해 이전의 모노레포 + 멀티레포 구성이 더 적합할 수 있습니다.
하지만 현재 프로젝트를 진행 중인 대부분의 팀이 이미 기존 Root 폴더 구조로 환경을 구성해 사용 중이었기 때문에, 새로운 모노레포 + 멀티레포 혼합 도입은 오히려 번거로울 것이라 판단하였습니다.
단일 앱만 관리하는 팀은 기존대로 workspace를 사용하고, 여러 앱을 관리하는 팀이라면 필요 시 Root 폴더에 .vscode 폴더만 추가하여 사용 가능하기 때문에 더 유리하다고 생각이 들었습니다.
현재 구조에 대한 고찰 및 향후 방향
현재 저희는 CI/CD 환경을 사용하고 있으나, 이는 기존 코드 기반으로 구성된 상태이고 nx를 통한 빌드 캐싱과 같은 기능은 아직 테스트해 보지 못한 상황입니다.
따라서 새롭게 구성한 pnpm + nx + 모노레포 + 멀티레포 기반에서 nx의 빌드 캐싱 성능이 실제로 체감될 정도로 효율적인지 충분히 검증한 후, 추가 도입 여부를 판단할 예정입니다.
6. 모노레포 도입기 – 3 : [Monorepo CLI] 복잡한 모노레포 설정 한번에 끝내기
마지막으로, 모노레포를 구성하며 마주했던 초기 설정의 불편함이 CLI 도구 개발로 발전한 과정을 소개하겠습니다.
모노레포는 여러 프로젝트를 단일 저장소에서 관리하여 코드 재사용성과 일관성을 높이는 강력한 개발 방식입니다. 하지만 ESLint, TypeScript, 경로 별칭(path Alias) 등 초기 설정의 복잡성은 많은 개발자에게 진입장벽으로 다가옵니다.
아쉽게도 Turborepo, Vite, React 19, Tailwind CSS 4, shadcn/ui 등 현재 저희 프로젝트에 필요한 설정이 모두 갖춰진 템플릿은 찾을 수 없었습니다. 따라서 모든 설정을 자동으로 구성해 줄 CLI 도구를 직접 만들었습니다.
CLI를 개발하면서 알게 된, Vite 기반 모노레포의 구조와 핵심 설정, 특히 TypeScript와 ESLint 구성을 어떻게 체계적으로 관리할 수 있는지 자세히 살펴보겠습니다.
6-1. 템플릿 준비하기
CLI를 개발하기 전에 재사용 가능한 Template을 설정해야 했습니다. Template은 여러 공식 문서 및 예제를 참고해서 만들었습니다.
🔷 프로젝트 구조
프로젝트 구조는 다음과 같습니다.
my-template/
├── apps
│ └── web/ # Vite + React 애플리케이션
├── packages
│ ├── eslint-config/ # 공유 ESLint 설정
│ ├── typescript-config/ # 공유 TypeScript 설정
│ └── ui/ # 공유 React 컴포넌트 (shadcn/ui)
└── turbo.json # Turborepo 설정 파일
apps/web
: 실제 사용자가 마주하는 웹 애플리케이션입니다.packages/ui
: 버튼, 카드 등 여러 앱에서 재사용할 수 있는 공통 UI 컴포넌트 라이브러리입니다.packages/eslint-config
: 모노레포 전체의 코드 스타일과 품질을 일관되게 유지하기 위한 중앙 ESLint 설정 패키지입니다.packages/typescript-config
: TypeScript 관련 설정을 한 곳에서 관리하여 모든 프로젝트가 동일한 컴파일러 옵션을 공유하도록 합니다.
🔷 ESLint 설정 (@workspace/eslint-config
)
Turborepo 기본 가이드에 따라 @workspace/eslint-config
패키지를 통해 중앙에서 관리되도록 구성했습니다. 기본적으로 각 앱에서 baseConfig를 import 하는 것으로 설정을 마칠 수 있습니다.
import baseConfig from '@workspace/eslint-config/base';
/** @type {import("eslint").Linter.Config} */
export default baseConfig;
baseConfig에는 관련된 패키지가 제공하는 권장 규칙을 우선적으로 채택했으며, 각 패키지별로 ESLint 설정을 명확하게 구분했습니다.
가독성을 가장 우선적으로 고려했으며, 프로젝트 상황에 따라서 커스텀을 쉽게 할 수 있습니다.
🔷 TypeScript 설정 (@workspace/typescript-config
)
tsconfig는 Typescript 선언을 어떻게 내보내고 구성할지 결정하는 중요한 설정이며, 동시에 모노레포 구성에 큰 어려움을 주는 요인 중 하나입니다. Vite를 위한 모노레포임을 고려해서, 설정을 단순화하기 위해 모노레포에서 사용할 패키지의 tsconfig를 3가지 종류로 정의했습니다.
설정 파일 | 용도 / 설명 | Target | Emit 옵션 |
---|---|---|---|
tsconfig.app.json | 실제 브라우저에서 동작하는 애플리케이션 코드 빌드용런타임 실행만 필요하며 JS 출력물은 Vite가 처리 | ES2022 | noEmit (출력물 생성 안 함) |
tsconfig.lib.json | 다른 앱에서 재사용할 라이브러리 패키지용 타입 선언 파일만 배포하도록 구성 | ES2022 | emitDeclarationOnly (d.ts만 생성) |
tsconfig.node.json | 개발 환경에서 Vite 실행 시 필요한 Node 관련 설정최신 Node 기능을 지원 | ES2023 | 기본값 사용 (emit 옵션 없음) |
🔷 공유 컴포넌트 사용 예제
아래와 같이 공유 컴포넌트를 별칭(alias)으로 참조할 수 있다는 예제도 템플릿에 첨부했습니다.
apps/web/src/App.tsx
import { Button } from '@workspace/ui/components/button';
export default function App() {
return <Button>Hello from the UI package!</Button>;
}
6-2. CLI 앱 개발하기
작성한 템플릿을 명령어에 따라 사용자 PC에 설치해주는 CLI를 개발했습니다.
commander.js
를 기반으로 CLI 인터페이스 및 인자를 정의하고 Command와 Action을 정의했습니다. 그리고 사용자 선택에 따라 미리 Storage에 업로드 해둔 템플릿을 다운로드 한 뒤, 사용자가 원하는 이름으로 재구성해주는 기능을 구현했습니다.

6-3. 신규 모노레포 생성하기
모노레포가 구성되지 않은 경로에서 CLI 앱을 실행하면 새로운 모노레포를 생성합니다.

6-4. 신규 앱 추가하기
현재 경로에 이미 구성된 모노레포가 있을 경우, 모노레포에 새로운 앱을 추가합니다.


6-5. 모노레포에서 템플릿 기반 개발
수동으로 동일한 템플릿을 구성하려면 10여 개의 명령어를 실행하고, 추가적인 세부 설정이 필요한데 이 모든 것이 1개의 명령어로 가능하게 되었습니다.
일관된 규칙, 공통 컴포넌트 및 공통 코드가 있으므로, 개발 시작 단계에서도 큰 노력이 들지 않았습니다. 덕분에 여러 개의 업무 목적 백오피스 및 테스트 앱을 빠르게 개발하고 사내에 배포할 수 있었습니다.
6-6. CLI 개발 경험 정리
CLI로 복잡하고 까다로운 모노레포 초기 설정 과정을 자동화해보았습니다.
템플릿을 만들어야 하는 만큼 TypeScript와 ESLint를 이해하고 적절한 공통 설정을 정의하는 데 노력을 많이 기울였는데, 이 과정은 개발자로서도 작은 성취감을 느끼게 해주었습니다.
앞으로 신규 프로젝트 구성이나 앱 추가가 필요한 순간마다 팀 전체 생산성을 높이는 자산으로 활용될 수 있을 것이라 기대합니다.
7. 마치며
이번 글에서는 다양한 모노레포 도입 경험을 공유했습니다. 모노레포는 단순한 코드 관리 방식을 넘어서는 의미를 가지며, 프로젝트의 규모와 팀의 특성에 따라 최적의 선택은 달라질 수 있습니다.
[Project A] 사례에서 보았듯이, Turborepo 같은 도구는 프로젝트의 빌드 시간을 획기적으로 단축하여 생산성을 높일 수 있습니다. 반면 [Project B] 사례에서는 모노레포가 오히려 유연성을 저해하는 경우가 있었고, 이에 따라 프로젝트 특성에 맞는 새로운 구조로 전환할 필요가 있었습니다. 마지막으로, [Monorepo CLI] 사례는 복잡한 설정을 자동화하여 팀 전체의 개발 효율을 개선한 좋은 예시였습니다.
모노레포는 만능 해결책이 아니며, 우리의 목표는 항상 프로젝트에 가장 적합한 개발 환경을 구축하는 것입니다. 이 글이 여러분의 프로젝트에 맞는 구조를 고민하고 선택하는 데 도움이 되기를 바랍니다.
읽어주셔서 감사합니다!