모노레포 전환 실전: Turborepo 도입부터 구성까지

이민영·2025년 4월 15일

모노레포

목록 보기
2/2

이전에 모놀리식 구조에서 모노레포 구조로 전환하기 위한 과정에 대한 고민들을 벨로그에 정리했었다.
오늘은 실제로 Turborepo를 사용해 모노레포를 구성한 과정을 정리해보고자 한다.

1. 왜 Turborepo인가?

우선 Turborepo를 선택하기 전에, 모노레포를 구축할 수 있는 방법에 대해 먼저 알아봤었다.
모노레포를 지원하는 도구들은 다양하게 존재하는데, 대표적으로는 Turborepo, Lerna, Nx, Yarn Workspaces, npm Workspaces, pnpm 등이 있다.
그 중에서도 실제 도입을 고민했던 도구는 Turborepo, Nx, npm Workspaces였다.

🔧 npm Workspaces

  • 이미 사내에서 기본적으로 사용하고 있던 패키지 매니저이기도 했고,
  • 별도의 설정 없이도 다중 패키지를 하나의 저장소에서 관리할 수 있다는 점이 간단하게 구성하기 좋은 거 같다.
  • 하지만 빌드 최적화, 캐싱, 병렬 처리 등의 기능은 별도로 구성해줘야 했고, 프로젝트가 커질수록 관리가 복잡해질 가능성이 있었다.

🧰 Nx

  • Angular 팀 출신들이 만든 도구로, 대규모 모노레포 운영에 특화된 고급 기능들을 제공한다.
  • 의존성 그래프를 자동으로 생성해서 변경된 부분만 빌드하거나 테스트하는 증분 실행이 가능하다.
  • React, Next.js, Angular, NestJS 등 다양한 프레임워크를 위한 플러그인 생태계가 잘 갖춰져 있어서, 팀의 기술 스택에 맞게 쉽게 확장 가능하다.
  • 공통된 기능을 라이브러리로 분리하고, 여러 프로젝트 간에 쉽게 공유할 수 있다.
  • 단점으로는 처음 접할 땐 설정이 복잡하게 느껴질 수 있고, 학습 곡선이 존재한다는 점이다. 하지만 복잡한 아키텍처와 대규모 프로젝트에선 오히려 강점이 될 수 있는 부분이다.

⚡ Turborepo

  • Vercel에서 개발한 모노레포 관리 도구로, 특히 프론트엔드 프로젝트에서 빠르게 떠오르고 있는 솔루션이다.
  • 간단한 설정만으로도 강력한 기능을 제공하여 빠르게 프로젝트를 시작할 수 있다.
  • 이전에 빌드된 아티팩트를 재사용하여 빌드 속도를 대폭 향상시키며, 변경된 부분만 선택적으로 빌드하는 증분 빌드를 지원하여 불필요한 작업을 최소화 한다.
  • 태스크 간의 의존성을 분석하여 병렬로 실행 가능한 태스크를 식별하고, 최적의 실행 순서를 결정한다.
  • React, Next.js, Vue, Node.js 등 다양한 스택에서 유연하게 활용 가능이 가능하다.
  • 하지만, Nx에 비하면 다양한 고급 기능(= 플러그인)들이 제공되지 않는다.

📌 왜 Turborepo를 선택했는가?

✅ 내가 중요하게 본 기준은 다음 두 가지였다:

1. 모노레포로 전환할 수 있는 시간이 넉넉하지 않았기 때문에, 학습 곡선이 완만해야 한다.
2. 빌드 속도가 빨라야 한다.

  • npm workspaces는 보일러플레이트가 적다는 점에서 좋았지만, 빌드 최적화를 직접 구성해야 한다는 점에서 손이 많이 갔다.
  • Nx는 고급 기능과 확장성 면에서 훌륭했지만, 설정이 복잡하고 학습 비용이 상대적으로 높았다.
  • 반면, Turborepo는 작고 빠르게 시작할 수 있고, 팀과 프로젝트의 구조를 크게 변경하지 않고도 바로 도입할 수 있다는 점에서 유리했다.
  • 추가로, Nx의 고급 기능들(예: 다양한 플러그인 등)은 우리 팀 상황에서는 아직 필요하지 않았고,
  • Turborepo는 Vercel이 관리하고 있다는 점에서 앞으로도 꾸준히 플러그인이나 기능이 발전할 것이라는 기대감도 있었다.

결론적으로, 적은 설정으로도 빠르고 효율적인 모노레포 환경을 만들 수 있다는 점에서 Turborepo를 선택하게 되었다.


2. Turborepo 기본 개념 이해하기

앞서 설명했던 Turborepo는 Vercel에서 만든 모노레포 빌드 시스템으로, 다중 패키지를 하나의 저장소에서 효과적으로 관리하고, 빠르게 빌드/배포할 수 있게 해주는 도구이다.

⚡ Turborepo는 어떤 문제를 해결하는가?

1. 느린 빌드 시간
매번 전체를 빌드하는 대신, 변경된 부분만 빌드하는 증분 빌드 (Incremental Build) 지원
→ 시간 절약 + 빠른 피드백

2. 반복 작업의 낭비
로컬 및 원격 캐시를 통해 이전에 실행한 작업 결과를 저장하고, 동일한 작업은 다시 실행하지 않음
→ 빌드/테스트/타입체크 등의 작업을 재사용

3. 작업 순서의 혼란과 비효율
작업 간의 의존성을 분석하는 의존성 그래프 (Dependency Graph) 기반으로 자동으로 실행 순서를 최적화
→ 순서 문제 해결 + 병렬 실행 가능

4. 복잡한 설정
최소한의 설정으로 바로 시작 가능하며, 필요에 따라 점진적으로 확장 가능
→ 처음 쓰는 팀에도 부담 없이 도입 가능


3. Turborepo 설치하기

🛠️ Turborepo 설치 방법

1. create-turbo를 이용해서 설치하기

가장 간편한 방법은 Turborepo에서 제공하는 템플릿을 사용하는 거다. 아래 명령어를 실행하면 프로젝트 이름과 패키지 매니저를 선택하라는 프롬프트가 나타나고, 기본적인 프로젝트 구조가 자동으로 생성된다.

npx create-turbo@latest

이 명령어를 실행하면 appspackages 폴더가 포함된 모노레포 구조가 생성되고, turbo.json 파일도 함께 생성된다.


2. 수동 설치

이미 존재하는 프로젝트에 Turborepo를 도입하려면 수동으로 설치할 수 있다.

npm install turbo --save-dev

설치 후, turbo.json 파일을 생성하고 필요한 설정을 추가하면 된다.


4. Turborepo 살펴보기

📁 프로젝트 폴더 구조 살펴보기

create-turbo를 이용해서 설치하면 Turborepo의 기본적인 폴더 구조는 아래와 같다.

  • apps: 실제 애플리케이션이 위치하는 폴더로, 예를 들어 web, docs 등의 프로젝트가 여기에 들어간다.

  • packages/: 여러 앱에서 공통으로 사용하는 패키지들을 모아두는 폴더이다. 예를 들어 UI 컴포넌트, ESLint 설정, TypeScript 설정 등이 여기에 위치한다.


⚙️ turbo.json 기본 구조와 의미

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 필드 설명

tasks 필드 안에서 프로젝트 내에서 사용할 작업(task) 들을 정의하며, 각 작업은 아래와 같은 설정을 통해 작동 방식을 지정할 수 있다.

  • dependsOn: 이 작업을 실행하기 전에 먼저 실행되어야 하는 작업을 지정한다.
    예: "^build"는 상위 의존 항목의 build 작업을 먼저 실행함을 의미한다.

  • inputs: 작업에 영향을 주는 파일 목록을 설정한다. 이 값이 변경되면 캐시가 무효화되어 작업이 다시 실행된다.
    예: "$TURBO_DEFAULT$"는 기본 입력값(package.json, tsconfig 등)을 의미하고, .env*는 환경변수 파일 변경도 감지하겠다는 의미.

  • outputs: 작업 결과로 생성되는 파일 경로를 명시한다. 이 결과는 캐싱되어, 다음 빌드 시 재사용된다.
    예: .next/**는 Next.js의 빌드 결과, !.next/cache/**는 캐싱 제외

  • cache: false로 설정하면 해당 작업은 캐시되지 않는다. 주로 dev와 같이 지속적으로 실행되는 작업에 사용된다.

  • persistent: true로 설정하면 작업이 영구적으로 실행된다. dev 서버처럼 종료되지 않고 계속 실행되어야 하는 작업에 사용된다.

turbo.json 추가 설정

"test": {
  "dependsOn": ["^test"],
  "outputs": []
}

위 코드 처럼 test, storybook, e2e 등 원하는 작업을 직접 추가할 수 있으며,
Turborepo는 이를 캐싱, 병렬 실행 등의 규칙에 따라 자동으로 관리해준다.


5. 워크스페이스 설정과 의존성 관리

모노레포를 구성할 때 핵심이 되는 기능 중 하나가 워크스페이스(Workspaces)이다.
워크스페이스를 활용하면 여러 개의 프로젝트(패키지)를 하나의 저장소 안에서 통합적으로 관리할 수 있도록 해주는 기능이다.
Turborepo는 npm, Yarn, pnpm의 워크스페이스 기능을 활용하여 하위 패키지를 인식하고 연결한다.

📦 워크스페이스란?

워크스페이스는 각각의 독립적인 패키지 단위를 의미하며, 각 패키지는 자신만의 package.json 파일을 가지고 있다.
앱, UI 컴포넌트 라이브러리, 유틸 함수 등 다양한 목적의 패키지를 분리해 구성할 수 있으며,
이러한 워크스페이스들을 하나의 저장소에서 효율적으로 관리하는 것이 바로 모노레포의 핵심이다.

워크스페이스에 정의된 의존성은 각 패키지의 package.json에 명시하지만, 실제 패키지는 루트의 node_modules에 통합 설치된다.
예를 들어 여러 워크스페이스에서 react 패키지를 공통으로 사용한다면, 해당 패키지는 한 번만 다운로드되어 루트에 설치된다.

이를 통해 다음과 같은 이점을 얻을 수 있다:

✅ 워크스페이스의 장점

  • 의존성 통합 관리
    루트에서 모든 워크스페이스의 의존성을 한 번에 설치하고 관리할 수 있다.

  • 중복 설치 방지
    공통된 패키지는 한 번만 설치되므로 설치 시간 단축과 디스크 공간 절약이 가능하다.

  • 로컬 개발에 유리
    워크스페이스 간 의존성을 symlink로 연결하므로, 코드 변경이 즉시 반영되어 빠른 피드백이 가능하다.

  • 코드 재사용 용이
    공통된 모듈을 별도의 패키지로 분리하여 여러 앱에서 쉽게 불러올 수 있어 유지보수성과 재사용성이 향상된다.


⚙️ 루트 package.json에서 설정하기

워크스페이스는 루트의 package.json 파일에서 아래와 같이 정의한다.

{
  "name": "my-turborepo",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ]
}

이렇게 설정 appspackages 디렉토리 내의 모든 하위 폴더들이 워크스페이스로 인식된다.

💡 "private": true는 워크스페이스를 사용하는 프로젝트에서 반드시 필요하다. (npm publish 방지용)


🔗 워크스페이스 간 의존성 관리

워크스페이스의 가장 큰 장점 중 하나는 로컬에서 패키지 간 의존성을 직접 연결할 수 있다는 점이다.
예를 들어, apps/web에서 packages/ui를 사용하고 싶다면 다음처럼 ui를 의존성으로 추가하면 된다:

// apps/web/package.json
{
  "dependencies": {
    "ui": "*"
  }
}

이 경우 실제 NPM에서 설치하는 것이 아니라, 로컬의 packages/ui 디렉토리를 참조하여 symlink로 연결된다.
이렇게 하면 버전 없이도 바로 사용 가능하고, 수정사항이 즉시 반영되어 개발 속도가 빨라진다.


워크스페이스는 모노레포의 기반을 이루는 중요한 개념이며, 단일 저장소에서 여러 프로젝트를 효율적으로 운영하고 협업하기 위한 기반이 된다.
Turborepo는 이 워크스페이스 구조 위에 캐싱, 병렬 실행, 증분 빌드 등의 강력한 기능을 얹어 모노레포의 생산성과 관리 효율성을 극대화를 해준다.


6. 실제로 써보며 느낀 점

기존 프로젝트를 Turborepo 구조에 맞게 옮기는 작업은 꽤 번거롭고 시간이 많이 걸렸다.
특히 설정 파일을 모듈화하거나 의존성을 정리하는 과정에서 생각보다 신경 써야 할 부분이 많았기 때문에, 도입 초기엔 "시간 대비 효과가 있을까?" 하는 의심도 있었다.

하지만 실제로 적용해보니 체감되는 변화가 꽤 컸다.

가장 먼저 눈에 띄는 건 빌드 속도 개선이었다.
Turborepo의 로컬/원격 캐시 기능 덕분에 변경되지 않은 작업은 캐시된 결과를 바로 재사용하고, 변경된 부분만 다시 빌드하기 때문에 전체 빌드 시간이 확연히 줄어들었다.
특히 전체 프로젝트를 다시 빌드해야 할 상황에서도 이전보다 훨씬 빠르게 완료되는 걸 보면서, 이점이 분명하다고 느꼈다.

또한, 모노레포 구조로 전환한 뒤로는 의존성 관리와 공통 모듈 재사용이 훨씬 명확해졌다.
이전에는 각 프로젝트마다 중복된 설정이나 유틸 코드가 흩어져 있었지만, packages/ 폴더에 공통 코드를 묶어두면서 한 곳에서 효율적으로 관리할 수 있게 됐다.
덕분에 코드 수정도 일관성 있게 반영되고, 팀원 간 충돌이나 버전 불일치 문제도 크게 줄어들었다.

🤝 팀 협업에 생긴 변화

구조 전환 이후, 팀원 간 협업도 훨씬 효율적으로 이루어지기 시작했다.
예전에는 A 개발자가 만든 공통 컴포넌트를 B 개발자가 다른 레포에서 사용하는 경우, 패키지를 따로 배포하거나 수동으로 옮겨야 했다.
하지만 이제는 같은 저장소 내에서 의존 관계를 바로 연결할 수 있고, 변경사항도 즉시 확인되기 때문에
"공통 컴포넌트 수정 → 테스트 → 반영"의 흐름이 훨씬 매끄러워졌다.

✍️ 결론

기에 구조를 바꾸는 데 드는 비용은 분명 존재하지만, 한 번 세팅해두면 장기적으로 관리가 훨씬 쉬워지고 협업 생산성도 올라간다.
모노레포를 고민하고 있다면 Turborepo는 진입 장벽이 낮으면서도 실용적인 선택지라고 생각한다. 특히 큰 설정 없이도 캐시, 병렬 빌드, 의존성 기반 실행 같은 고급 기능을 바로 활용할 수 있다는 점에서 추천할 만하다.

profile
Frontend Developer

0개의 댓글