Turborepo로 React Next.js TypeScript 모노레포 구축하기
Turborepo를 활용하여 React, Next.js, TypeScript 기반의 효율적인 모노레포 환경을 구축하는 실전 가이드입니다. 코드 공유부터 빌드 최적화까지 단계별로 설명합니다.
Turborepo로 React Next.js TypeScript 모노레포 구축하기
현대 웹 개발에서 여러 프로젝트를 효율적으로 관리하는 것은 필수적인 과제입니다. Turborepo는 Vercel에서 개발한 고성능 모노레포 도구로, React와 Next.js 프로젝트를 하나의 저장소에서 체계적으로 관리할 수 있게 해줍니다. 이 글에서는 Turborepo를 활용하여 TypeScript 기반의 모노레포 환경을 구축하는 방법을 실전 코드와 함께 살펴보겠습니다.
Turborepo 소개 및 장점
Turborepo는 JavaScript와 TypeScript 생태계를 위한 고성능 빌드 시스템입니다. 기존의 Lerna나 Nx와 달리 캐싱과 병렬 실행에 특화되어 있어 대규모 모노레포에서도 빠른 빌드 성능을 제공합니다.
주요 장점은 다음과 같습니다:
- 인크리멘털 빌드: 변경된 패키지만 다시 빌드
- 원격 캐싱: 팀원 간 빌드 결과 공유
- 병렬 실행: 의존성 그래프 기반 최적화된 작업 실행
- 간단한 설정: 복잡한 설정 없이 빠른 시작 가능
프로젝트 초기 설정
먼저 Turborepo를 사용하여 새로운 모노레포를 생성해보겠습니다.
npx create-turbo@latest my-turborepo
cd my-turborepo
생성된 프로젝트 구조는 다음과 같습니다:
my-turborepo/
├── apps/
│ ├── docs/ # Next.js 문서 앱
│ └── web/ # Next.js 메인 앱
├── packages/
│ ├── ui/ # 공유 UI 컴포넌트
│ ├── eslint-config/ # ESLint 설정
│ └── tsconfig/ # TypeScript 설정
├── package.json
└── turbo.json
공유 UI 컴포넌트 라이브러리 구축
모노레포의 핵심은 코드 재사용성입니다. packages/ui에 공유 컴포넌트를 구축해보겠습니다.
// packages/ui/src/Button.tsx
import React from 'react';
interface ButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary';
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
onClick
}) => {
const baseStyles = 'px-4 py-2 rounded font-medium transition-colors';
const variantStyles = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300'
};
return (
<button
className={`${baseStyles} ${variantStyles[variant]}`}
onClick={onClick}
>
{children}
</button>
);
};
// packages/ui/src/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
// packages/ui/package.json
{
"name": "ui",
"version": "0.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
},
"devDependencies": {
"@types/react": "^18.0.0",
"react": "^18.0.0",
"tsup": "^6.0.0",
"typescript": "^4.9.0"
},
"peerDependencies": {
"react": "^18.0.0"
}
}
TypeScript 설정 공유
TypeScript 설정을 중앙화하여 일관성을 유지할 수 있습니다.
// packages/tsconfig/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"composite": false,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"moduleResolution": "node",
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true,
"target": "es2017"
},
"exclude": [
"node_modules"
]
}
// packages/tsconfig/nextjs.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"extends": "./base.json",
"compilerOptions": {
"allowJs": true,
"declaration": false,
"declarationMap": false,
"incremental": true,
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "es6"],
"module": "esnext",
"noEmit": true,
"resolveJsonModule": true,
"target": "es5"
},
"include": [
"src",
"next-env.d.ts"
],
"exclude": [
"node_modules"
]
}Next.js 앱에서 공유 컴포넌트 사용
이제 메인 앱에서 공유 UI 컴포넌트를 사용해보겠습니다.
// apps/web/pages/index.tsx
import { Button } from 'ui';
export default function Home() {
const handleClick = () => {
alert('Button clicked!');
};
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold mb-8">
Welcome to Turborepo
</h1>
<div className="space-x-4">
<Button variant="primary" onClick={handleClick}>
Primary Button
</Button>
<Button variant="secondary" onClick={handleClick}>
Secondary Button
</Button>
</div>
</div>
</div>
);
}
// apps/web/package.json
{
"name": "web",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"start": "next start"
},
"dependencies": {
"next": "^13.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"ui": "*"
},
"devDependencies": {
"@types/node": "^18.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"tsconfig": "*",
"typescript": "^4.9.0"
}
}
Turbo.json 파이프라인 설정
Turborepo의 핵심인 파이프라인을 설정하여 빌드 최적화를 구현합니다.
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^lint"]
},
"type-check": {
"dependsOn": ["^type-check"]
},
"test": {
"dependsOn": ["^test"],
"outputs": ["coverage/**"]
}
}
}
이 설정으로 다음과 같은 최적화가 가능합니다:
dependsOn: ["^build"]: 의존성 패키지가 먼저 빌드됨outputs: 캐시할 출력 파일 지정cache: false: 개발 서버는 캐시하지 않음
스크립트 실행 및 캐싱 활용
루트 디렉토리에서 모든 패키지를 대상으로 명령을 실행할 수 있습니다.
# 모든 패키지 빌드
npm run build
# 특정 패키지만 빌드
npx turbo run build --filter=web
# 병렬 개발 서버 실행
npm run dev
# 린트 검사
npx turbo run lint
# 타입 체크
npx turbo run type-check
Turborepo는 실행 결과를 자동으로 캐시하므로, 변경사항이 없으면 이전 결과를 재사용하여 빌드 시간을 크게 단축시킵니다.
원격 캐싱 설정
팀 협업을 위해 Vercel의 원격 캐싱을 설정할 수 있습니다.
# Vercel 계정 연결
npx turbo login
# 원격 캐싱 연결
npx turbo link
이제 팀원들이 동일한 코드를 빌드할 때 캐시된 결과를 공유받아 빌드 시간을 절약할 수 있습니다.
마무리
Turborepo를 활용한 모노레포 구축을 통해 React와 Next.js 프로젝트를 효율적으로 관리할 수 있습니다. 공유 컴포넌트 라이브러리 구축, TypeScript 설정 통합, 그리고 강력한 캐싱 시스템을 통해 개발 생산성을 크게 향상시킬 수 있습니다. 특히 대규모 프로젝트나 마이크로 프론트엔드 아키텍처를 구현할 때 Turborepo의 진가를 발휘할 수 있을 것입니다.
관련 게시글
Vite Build Tool: Fast Frontend Development Guide
Vite는 현대적인 프론트엔드 개발을 위한 빠르고 효율적인 빌드 도구입니다. 이 가이드에서는 Vite의 핵심 기능, React 및 TypeScript 프로젝트 설정, 플러그인 활용법, 그리고 빌드 최적화 전략까지 완벽하게 다룹니다.
React Server Components (RSC) 심층 가이드: Next.js와 함께하는 Full-stack React
React Server Components (RSC)의 개념, 등장 배경, 동작 원리, 그리고 Next.js 13+ App Router에서의 활용법을 심층적으로 다룹니다. 클라이언트/서버 컴포넌트 분리 전략과 실전 코드 예제를 통해 RSC의 강력한 이점을 이해하고 웹 애플리케이션 성능을 최적화하는 방법을 알아봅니다.
Next.js Middleware: 강력한 요청 처리 활용법
Next.js Middleware를 활용하여 사용자 인증, 국제화, A/B 테스트 등 다양한 요청 처리 로직을 효율적으로 구현하는 방법을 심층적으로 알아봅니다. 실전 코드 예제를 통해 Next.js 애플리케이션의 프론트엔드 기능을 강화하세요.