Turborepo 모노레포 빌드 최적화: 로컬·원격 캐시로 빌드 시간 90% 단축

프론트엔드

Turborepo모노레포빌드 최적화원격 캐시pnpm

이 글은 누구를 위한 것인가

  • 모노레포 빌드 시간이 너무 길어 개선이 필요한 팀
  • Turborepo 파이프라인과 캐시 전략을 설계하는 개발자
  • CI/CD에서 원격 캐시를 활용해 빌드 속도를 높이려는 팀

들어가며

모노레포에서 패키지가 늘어날수록 빌드 시간은 선형적으로 증가한다. Turborepo는 태스크 의존성 그래프를 분석해 병렬 실행하고, 변경되지 않은 패키지의 결과를 캐시해서 증분 빌드를 실현한다.

이 글은 bluefoxdev.kr의 Turborepo 최적화 가이드 를 참고하여 작성했습니다.


1. Turborepo 파이프라인 설계

[Turborepo 핵심 개념]

Task Pipeline:
  - 패키지 간 태스크 의존성 정의
  - "^build": 의존 패키지의 build 먼저 실행
  - "build": 같은 패키지 내 다른 태스크

Cache 동작:
  입력(inputs) 해시 → 캐시 히트 → 결과 복원
  inputs: 소스 파일, 환경변수, package.json
  outputs: 빌드 결과물 (dist/, .next/ 등)

원격 캐시:
  로컬 캐시 (~/.turbo) → 팀 공유 불가
  원격 캐시 (Vercel/자체 서버) → CI + 팀원 공유

[모노레포 구조]
  apps/
    web/          Next.js 앱
    api/          Express API
    admin/        React 관리자
  packages/
    ui/           공유 UI 컴포넌트
    config/       ESLint, TypeScript 설정
    utils/        공유 유틸리티
    schemas/      Zod 스키마 공유

[의존성 그래프 예시]
  web → ui, utils, schemas
  api → utils, schemas
  admin → ui, utils
  
  turbo build → ui/build, utils/build 병렬
             → schemas/build (utils 완료 후)
             → web/build, api/build, admin/build 병렬

2. Turborepo 구현

// turbo.json - 루트 설정
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [
    ".env",
    "tsconfig.json"
  ],
  "globalEnv": [
    "NODE_ENV",
    "VERCEL_ENV"
  ],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": [
        "$TURBO_DEFAULT$",
        ".env.local"
      ],
      "outputs": [
        "dist/**",
        ".next/**",
        "!.next/cache/**"
      ]
    },
    "test": {
      "dependsOn": ["^build"],
      "inputs": [
        "src/**",
        "tests/**",
        "vitest.config.ts"
      ],
      "outputs": [
        "coverage/**"
      ],
      "cache": true
    },
    "lint": {
      "inputs": [
        "src/**",
        ".eslintrc.*",
        "eslint.config.*"
      ],
      "outputs": []
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "inputs": [
        "src/**",
        "tsconfig.json"
      ],
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
// package.json (루트)
{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "lint": "turbo lint",
    "test": "turbo test",
    "typecheck": "turbo typecheck",
    "clean": "turbo clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^2.0.0"
  },
  "packageManager": "pnpm@9.0.0"
}
// packages/ui/package.json
{
  "name": "@myapp/ui",
  "main": "./dist/index.js",
  "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",
    "dev": "tsup src/index.ts --format esm,cjs --dts --watch"
  }
}
# 원격 캐시 설정 (Vercel)
# 1. Vercel 원격 캐시 연결
npx turbo login
npx turbo link

# 2. 자체 원격 캐시 서버 (Ducktape)
# .env
TURBO_TOKEN=your-token
TURBO_TEAM=your-team
TURBO_API=https://your-cache-server.com

# 3. CI에서 원격 캐시 활용 (GitHub Actions)
# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
    
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # 변경 감지를 위해
      
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      
      - run: pnpm install --frozen-lockfile
      
      # 변경된 패키지만 필터링
      - name: Build affected packages
        run: pnpm turbo build --filter="...[HEAD^1]"
      
      - name: Test affected packages
        run: pnpm turbo test --filter="...[HEAD^1]"
      
      - name: Lint & Typecheck all
        run: pnpm turbo lint typecheck
# turbo 주요 CLI 옵션
# 특정 앱만 빌드 (의존성 포함)
turbo build --filter=web

# 변경된 패키지만 (Git 기반)
turbo build --filter="...[HEAD^1]"

# 특정 패키지와 그 의존자들
turbo build --filter="...@myapp/ui"

# 캐시 무시하고 강제 재빌드
turbo build --force

# 병렬 실행 수 제한 (메모리 부족 시)
turbo build --concurrency=4

# 태스크 그래프 시각화
turbo build --graph=graph.svg

# 실행 요약 출력
turbo build --summarize

마무리

Turborepo의 핵심 가치는 두 가지다. 첫째, "dependsOn": ["^build"]로 패키지 간 빌드 순서를 자동 관리하고 병렬 실행으로 최적화한다. 둘째, 원격 캐시로 CI와 팀원 간 빌드 결과를 공유해 "내 로컬에서 빌드된 결과를 CI가 재사용"한다. --filter="...[HEAD^1]"로 PR의 변경 영향권 패키지만 빌드/테스트해서 CI 시간을 극적으로 단축한다.