노트

Turborepo

Turborepo

인프라#tooling#ci · 연결된 개념 6개

쉽게 말하면

Turborepo는 푼 문제의 답을 노트에 적어 두는 학생 같아요. 같은 문제, 즉 같은 입력이 다시 나오면 또 풀지 않고 적어 둔 답을 꺼내고, 서로 상관없는 문제는 동시에 풀어서 모노레포 빌드가 빨라져요.

비유의 한계 학생과 달리 무엇이 같은 문제인지 Turborepo가 다 알진 못해요. 빌드에 영향을 주는 환경변수는 env에 적어야 해시에 들어가고, outputs를 빠뜨리면 캐시가 결과물을 복원하지 못해요.

Turborepo는 JavaScript·TypeScript 모노레포의 태스크(빌드·린트·테스트)를 빠르게 돌리는 빌드 시스템이다. 기존 package.json 스크립트는 그대로 두고, 스크립트 사이의 의존 관계만 알려 주면 병렬 실행과 캐싱을 맡는다. 의도적으로 "빠른 실행과 캐시" 한 가지에 집중한다.

태스크 그래프와 병렬 실행

turbo.json에 태스크 사이의 관계를 선언하면, Turborepo는 그 그래프가 허락하는 만큼 병렬로 돌린다.

{
  "tasks": {
    "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
    "test": { "dependsOn": ["build"] },
    "deploy": { "dependsOn": ["utils#build"], "cache": false },
    "dev": { "persistent": true, "cache": false },
    "lint": {}
  }
}
  • ^build: 의존하는 패키지들의 build가 먼저다(위상 순서). 라이브러리를 먼저 빌드하고 그것을 쓰는 앱을 빌드한다
  • build(접두사 없음): 같은 패키지 안의 순서다. test 전에 그 패키지의 build를 끝낸다
  • utils#build: 특정 패키지의 태스크를 기다린다
  • persistent: true: dev 서버처럼 끝나지 않는 태스크다. 다른 태스크가 이것에 의존할 수 없다. 함께 띄울 태스크는 with에 적는다
  • 동시 실행 수는 --concurrency로 정한다. 기본값은 10이고 100%면 논리 코어 수만큼 쓴다. 그래프를 무시하는 --parallel은 deprecated다(2026 기준)
  • 타입 검사처럼 "의존 패키지가 바뀌면 다시 돌아야 하지만 순서는 상관없는" 태스크는 transit 노드로 푼다. 스크립트가 없는 빈 태스크 transit: { "dependsOn": ["^transit"] }를 두고 check-types: { "dependsOn": ["transit"] }로 걸면, 의존 관계는 해시에 반영되면서 실행은 병렬로 된다. ^check-types로 걸면 캐시는 정확하지만 직렬로 돈다

캐시: 무엇으로 해시를 만들고 무엇을 저장하나

해시는 두 층이고 어느 쪽이 바뀌어도 미스(miss)다.

층들어가는 것
전역 해시(모든 태스크)루트 turbo.json의 태스크 정의, 루트 lockfile 변경, globalDependencies에 적은 파일(예: .env), globalEnv 변수 값, 동작을 바꾸는 플래그, -- 뒤의 패스스루 인자
패키지 해시(그 패키지)패키지의 package.json과 turbo.json, 그 패키지 의존성에 해당하는 lockfile 부분, 소스 파일(기본은 git이 추적하는 전부, inputs로 좁힘), env 변수 값
  • 저장하는 것은 outputs에 적은 파일과 터미널 로그다. 히트(hit)면 파일을 복원하고 로그를 재생한다. 모든 태스크가 히트면 FULL TURBO로 끝난다
  • inputs를 적으면 기본 동작(.gitignore 존중 포함)이 사라진다. 기본에 덧붙이려면 ["$TURBO_DEFAULT$", "!README.md"]처럼 쓴다
  • 환경변수는 기본이 strict 모드다. env·globalEnv·passThroughEnv에 적지 않은 변수는 해시에도 안 들어가고 태스크에도 전달되지 않는다. passThroughEnv는 해시에는 넣지 않고 런타임에만 넘기는 변수다(예: NODE_OPTIONS). Next.js의 NEXT_PUBLIC_*, Vite의 VITE_*는 프레임워크 추론으로 자동 포함된다. .env 파일은 로드해 주지 않으니 해시에 넣으려면 inputs에 .env*를 적는다
  • 로컬 캐시는 .turbo/cache에 있고, turbo login → turbo link로 원격 캐시(remote caching)를 켜면 팀과 CI가 결과물을 공유한다. CI가 main에서 이미 빌드한 패키지를 내 컴퓨터는 다시 빌드하지 않는다
  • 미스 원인은 --dry=json(태스크별 해시·inputs·outputs)과 --summarize(.turbo/runs에 실행 요약 저장)로 두 번 실행을 비교해 찾는다

바뀐 것만 돌리기

turbo run test --affected                     # main...HEAD 사이에 바뀐 패키지와 그 의존자
turbo run build --filter=...[origin/main]     # 비슷한 뜻을 filter 문법으로
turbo run build --filter=@acme/ui...          # ui와 ui가 의존하는 것
turbo run lint --filter=./apps/* --filter=!./apps/admin

--affected의 기준 커밋은 TURBO_SCM_BASE로 바꾼다. CI에서 얕은 clone이면 base와 head 사이 이력이 없어 전부 바뀐 것으로 본다.

하지 않는 것

  • 코드 생성기와 아키텍처 규칙은 거의 없다. turbo boundaries가 패키지 디렉터리 밖 파일 import와 package.json에 없는 의존성 import를 잡고 태그 기반 allow/deny 규칙을 받지만, 실험 단계다(2026 기준). 그 이상은 Nx의 모듈 경계 규칙이나 별도 린트 규칙을 쓴다
  • 캐시는 태스크가 결정적일 때만 안전하다. 라이브 백엔드를 치는 e2e처럼 입력이 같아도 결과가 달라지는 태스크는 cache: false로 둔다. 원격 왕복보다 빠른 태스크나 결과물이 아주 큰 태스크는 캐시가 오히려 느릴 수 있다
  • 잘 맞는 곳: 패키지 수가 수~수십 개인 JS·TS 저장소, pnpm 워크스페이스와 함께 쓸 때. 덜 맞는 곳: 복잡한 코드 생성, 여러 CI 머신에 작업 분산, JS 외 언어가 중심인 경우

흔히 권하는 순서는 워크스페이스로 시작 → 빌드가 느려지면 Turborepo → 더 많은 구조가 필요하면 Nx다(2026 기준). 입력 해시로 결과를 재사용하는 원리는 Dockerfile: 레이어 캐시와 멀티 스테이지의 레이어 캐시와 같고, 모노레포 자체는 모노레포, 번들러와의 관계는 번들러를 본다.

출처: Turborepo: Caching · Configuring tasks · Using environment variables · turbo run 레퍼런스 · Remote Caching · turbo boundaries

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • "use cache"와 Cache Components

    'use cache'는 Next.js 16의 Cache Components 모델에서 async 함수나 컴포넌트의 반환값을 캐시하라고 명시하는 지시어(Directive)다. 아무것도 표시하지 않으면 매 요청 실행되고, 캐시하고 싶은 곳에만 직접 붙인다(옵트인, Opt-in).

  • tsconfig 핵심 옵션

    tsconfig.json은 TypeScript 컴파일러(tsc)에게 어떤 파일을, 어떤 규칙으로 검사하고, 무엇을 출력할지 알려 주는 프로젝트 설정 파일이다. tsc --init으로 만들고, 이 파일이 있으면 tsc만 쳐도 프로젝트 전체를 컴파일한다(-w는 감시 모드, watch mode).

  • 쿠버네티스 핵심 개념

    쿠버네티스는 여러 서버에 컨테이너를 배치하고, 죽으면 다시 띄우고, 트래픽에 맞춰 복제 수를 조절하는 컨테이너 오케스트레이션(container orchestration) 시스템이다. 사람이 하던 서버 운영을 "원하는 상태를 선언하면 계속 맞춰 주는" 제어 루프(control loop)로 자동화한다.

  • 멀티 에이전트 토폴로지

    여러 LLM 에이전트를 어떤 모양으로 연결해 일을 나눌지 정하는 구조. 단일, 수퍼바이저, 파이프라인, 스웜으로 나뉘고, 대부분의 일은 단일 에이전트로 시작하는 편이 낫다.

  • 브라우저 캐시

    한 번 받은 리소스(HTML·CSS·JS·이미지)를 브라우저가 저장해 두었다가 다시 쓰는 기능. 서버가 응답 헤더로 얼마나, 어떻게 캐시할지 알려 주고 브라우저가 그대로 따른다.

보기 옵션