이전에 모놀리식 구조에서 모노레포 구조로 전환하기 위한 과정에 대한 고민들을 벨로그에 정리했었다.
오늘은 실제로 Turborepo를 사용해 모노레포를 구성한 과정을 정리해보고자 한다.
우선 Turborepo를 선택하기 전에, 모노레포를 구축할 수 있는 방법에 대해 먼저 알아봤었다.
모노레포를 지원하는 도구들은 다양하게 존재하는데, 대표적으로는 Turborepo, Lerna, Nx, Yarn Workspaces, npm Workspaces, pnpm 등이 있다.
그 중에서도 실제 도입을 고민했던 도구는 Turborepo, Nx, npm Workspaces였다.
✅ 내가 중요하게 본 기준은 다음 두 가지였다:
1. 모노레포로 전환할 수 있는 시간이 넉넉하지 않았기 때문에, 학습 곡선이 완만해야 한다.
2. 빌드 속도가 빨라야 한다.
결론적으로, 적은 설정으로도 빠르고 효율적인 모노레포 환경을 만들 수 있다는 점에서 Turborepo를 선택하게 되었다.
앞서 설명했던 Turborepo는 Vercel에서 만든 모노레포 빌드 시스템으로, 다중 패키지를 하나의 저장소에서 효과적으로 관리하고, 빠르게 빌드/배포할 수 있게 해주는 도구이다.
1. 느린 빌드 시간
매번 전체를 빌드하는 대신, 변경된 부분만 빌드하는 증분 빌드 (Incremental Build) 지원
→ 시간 절약 + 빠른 피드백
2. 반복 작업의 낭비
로컬 및 원격 캐시를 통해 이전에 실행한 작업 결과를 저장하고, 동일한 작업은 다시 실행하지 않음
→ 빌드/테스트/타입체크 등의 작업을 재사용
3. 작업 순서의 혼란과 비효율
작업 간의 의존성을 분석하는 의존성 그래프 (Dependency Graph) 기반으로 자동으로 실행 순서를 최적화
→ 순서 문제 해결 + 병렬 실행 가능
4. 복잡한 설정
최소한의 설정으로 바로 시작 가능하며, 필요에 따라 점진적으로 확장 가능
→ 처음 쓰는 팀에도 부담 없이 도입 가능
가장 간편한 방법은 Turborepo에서 제공하는 템플릿을 사용하는 거다. 아래 명령어를 실행하면 프로젝트 이름과 패키지 매니저를 선택하라는 프롬프트가 나타나고, 기본적인 프로젝트 구조가 자동으로 생성된다.
npx create-turbo@latest
이 명령어를 실행하면 apps와 packages 폴더가 포함된 모노레포 구조가 생성되고, turbo.json 파일도 함께 생성된다.
이미 존재하는 프로젝트에 Turborepo를 도입하려면 수동으로 설치할 수 있다.
npm install turbo --save-dev
설치 후, turbo.json 파일을 생성하고 필요한 설정을 추가하면 된다.
create-turbo를 이용해서 설치하면 Turborepo의 기본적인 폴더 구조는 아래와 같다.

apps: 실제 애플리케이션이 위치하는 폴더로, 예를 들어 web, docs 등의 프로젝트가 여기에 들어간다.
packages/: 여러 앱에서 공통으로 사용하는 패키지들을 모아두는 폴더이다. 예를 들어 UI 컴포넌트, ESLint 설정, TypeScript 설정 등이 여기에 위치한다.
turbo.json 파일은 Turborepo의 핵심 설정 파일로,
각 작업(task) 간의 의존성, 입력/출력 정의, 캐싱 전략, 병렬 실행 여부 등을 정의한다.
이 설정을 통해 변경된 부분만 빠르게 빌드하고, 작업 순서를 자동으로 계산하며, 중복 작업을 캐싱으로 건너뛴다.
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": { //프로젝트 내에서 사용할 각 작업(task)을 정의
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"check-types": {
"dependsOn": ["^check-types"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
tasks 필드 안에서 프로젝트 내에서 사용할 작업(task) 들을 정의하며, 각 작업은 아래와 같은 설정을 통해 작동 방식을 지정할 수 있다.
dependsOn: 이 작업을 실행하기 전에 먼저 실행되어야 하는 작업을 지정한다.
예: "^build"는 상위 의존 항목의 build 작업을 먼저 실행함을 의미한다.
inputs: 작업에 영향을 주는 파일 목록을 설정한다. 이 값이 변경되면 캐시가 무효화되어 작업이 다시 실행된다.
예: "$TURBO_DEFAULT$"는 기본 입력값(package.json, tsconfig 등)을 의미하고, .env*는 환경변수 파일 변경도 감지하겠다는 의미.
outputs: 작업 결과로 생성되는 파일 경로를 명시한다. 이 결과는 캐싱되어, 다음 빌드 시 재사용된다.
예: .next/**는 Next.js의 빌드 결과, !.next/cache/**는 캐싱 제외
cache: false로 설정하면 해당 작업은 캐시되지 않는다. 주로 dev와 같이 지속적으로 실행되는 작업에 사용된다.
persistent: true로 설정하면 작업이 영구적으로 실행된다. dev 서버처럼 종료되지 않고 계속 실행되어야 하는 작업에 사용된다.
"test": {
"dependsOn": ["^test"],
"outputs": []
}
위 코드 처럼 test, storybook, e2e 등 원하는 작업을 직접 추가할 수 있으며,
Turborepo는 이를 캐싱, 병렬 실행 등의 규칙에 따라 자동으로 관리해준다.
모노레포를 구성할 때 핵심이 되는 기능 중 하나가 워크스페이스(Workspaces)이다.
워크스페이스를 활용하면 여러 개의 프로젝트(패키지)를 하나의 저장소 안에서 통합적으로 관리할 수 있도록 해주는 기능이다.
Turborepo는 npm, Yarn, pnpm의 워크스페이스 기능을 활용하여 하위 패키지를 인식하고 연결한다.
워크스페이스는 각각의 독립적인 패키지 단위를 의미하며, 각 패키지는 자신만의 package.json 파일을 가지고 있다.
앱, UI 컴포넌트 라이브러리, 유틸 함수 등 다양한 목적의 패키지를 분리해 구성할 수 있으며,
이러한 워크스페이스들을 하나의 저장소에서 효율적으로 관리하는 것이 바로 모노레포의 핵심이다.
워크스페이스에 정의된 의존성은 각 패키지의 package.json에 명시하지만, 실제 패키지는 루트의 node_modules에 통합 설치된다.
예를 들어 여러 워크스페이스에서 react 패키지를 공통으로 사용한다면, 해당 패키지는 한 번만 다운로드되어 루트에 설치된다.
이를 통해 다음과 같은 이점을 얻을 수 있다:
의존성 통합 관리
루트에서 모든 워크스페이스의 의존성을 한 번에 설치하고 관리할 수 있다.
중복 설치 방지
공통된 패키지는 한 번만 설치되므로 설치 시간 단축과 디스크 공간 절약이 가능하다.
로컬 개발에 유리
워크스페이스 간 의존성을 symlink로 연결하므로, 코드 변경이 즉시 반영되어 빠른 피드백이 가능하다.
코드 재사용 용이
공통된 모듈을 별도의 패키지로 분리하여 여러 앱에서 쉽게 불러올 수 있어 유지보수성과 재사용성이 향상된다.
워크스페이스는 루트의 package.json 파일에서 아래와 같이 정의한다.
{
"name": "my-turborepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
이렇게 설정 apps와 packages 디렉토리 내의 모든 하위 폴더들이 워크스페이스로 인식된다.
💡 "private": true는 워크스페이스를 사용하는 프로젝트에서 반드시 필요하다. (npm publish 방지용)
워크스페이스의 가장 큰 장점 중 하나는 로컬에서 패키지 간 의존성을 직접 연결할 수 있다는 점이다.
예를 들어, apps/web에서 packages/ui를 사용하고 싶다면 다음처럼 ui를 의존성으로 추가하면 된다:
// apps/web/package.json
{
"dependencies": {
"ui": "*"
}
}
이 경우 실제 NPM에서 설치하는 것이 아니라, 로컬의 packages/ui 디렉토리를 참조하여 symlink로 연결된다.
이렇게 하면 버전 없이도 바로 사용 가능하고, 수정사항이 즉시 반영되어 개발 속도가 빨라진다.
워크스페이스는 모노레포의 기반을 이루는 중요한 개념이며, 단일 저장소에서 여러 프로젝트를 효율적으로 운영하고 협업하기 위한 기반이 된다.
Turborepo는 이 워크스페이스 구조 위에 캐싱, 병렬 실행, 증분 빌드 등의 강력한 기능을 얹어 모노레포의 생산성과 관리 효율성을 극대화를 해준다.
기존 프로젝트를 Turborepo 구조에 맞게 옮기는 작업은 꽤 번거롭고 시간이 많이 걸렸다.
특히 설정 파일을 모듈화하거나 의존성을 정리하는 과정에서 생각보다 신경 써야 할 부분이 많았기 때문에, 도입 초기엔 "시간 대비 효과가 있을까?" 하는 의심도 있었다.
하지만 실제로 적용해보니 체감되는 변화가 꽤 컸다.
가장 먼저 눈에 띄는 건 빌드 속도 개선이었다.
Turborepo의 로컬/원격 캐시 기능 덕분에 변경되지 않은 작업은 캐시된 결과를 바로 재사용하고, 변경된 부분만 다시 빌드하기 때문에 전체 빌드 시간이 확연히 줄어들었다.
특히 전체 프로젝트를 다시 빌드해야 할 상황에서도 이전보다 훨씬 빠르게 완료되는 걸 보면서, 이점이 분명하다고 느꼈다.
또한, 모노레포 구조로 전환한 뒤로는 의존성 관리와 공통 모듈 재사용이 훨씬 명확해졌다.
이전에는 각 프로젝트마다 중복된 설정이나 유틸 코드가 흩어져 있었지만, packages/ 폴더에 공통 코드를 묶어두면서 한 곳에서 효율적으로 관리할 수 있게 됐다.
덕분에 코드 수정도 일관성 있게 반영되고, 팀원 간 충돌이나 버전 불일치 문제도 크게 줄어들었다.
구조 전환 이후, 팀원 간 협업도 훨씬 효율적으로 이루어지기 시작했다.
예전에는 A 개발자가 만든 공통 컴포넌트를 B 개발자가 다른 레포에서 사용하는 경우, 패키지를 따로 배포하거나 수동으로 옮겨야 했다.
하지만 이제는 같은 저장소 내에서 의존 관계를 바로 연결할 수 있고, 변경사항도 즉시 확인되기 때문에
"공통 컴포넌트 수정 → 테스트 → 반영"의 흐름이 훨씬 매끄러워졌다.
기에 구조를 바꾸는 데 드는 비용은 분명 존재하지만, 한 번 세팅해두면 장기적으로 관리가 훨씬 쉬워지고 협업 생산성도 올라간다.
모노레포를 고민하고 있다면 Turborepo는 진입 장벽이 낮으면서도 실용적인 선택지라고 생각한다. 특히 큰 설정 없이도 캐시, 병렬 빌드, 의존성 기반 실행 같은 고급 기능을 바로 활용할 수 있다는 점에서 추천할 만하다.