개요
- ABC User Feedback: LINE+ ABC Studio에서 개발한 오픈소스 풀 스택 웹 애플리케이션
- 온프레미스(on-premise) 설치형 솔루션
- 제공 기능: 사용자 피드백 수집 API + 피드백 시각화 관리자 웹 화면
- 전체 스택을 TypeScript 단일 언어로 개발 → 모노리포 구조 채택
모노리포 구조 선택
구조 비교
| 구조 | 설명 | 장점 | 단점 |
|---|---|---|---|
| 개별 프로젝트 구조 | 프론트엔드/백엔드를 각각 독립 패키지로 단순히 한 저장소에 배치 | 프로젝트 간 명확한 분리 | 프로젝트 간 코드 공유 어려움 |
| 단일 프로젝트 구조 | 하나의 프로젝트 안에 여러 서브 패키지 구성 (apps/, packages/) |
코드 일관성, 재사용성 향상, 관심사 분리, 효율적 빌드/배포 | 패키지 간 의존성 관리 필요 |
| 단일 프로젝트 + tooling 디렉터리 | apps, packages, tooling 세 디렉터리로 분리 |
설정 중앙화, 일관성 극대화 | 초기 구조 설계 비용 |
채택된 구조 (ABC User Feedback)
apps
ㄴ web
ㄴ api
packages
ㄴ ufb-shared // @ufb/shared
ㄴ ufb-tailwind // @ufb/tailwind
ㄴ ufb-ui // @ufb/ui
tooling
ㄴ eslint // @ufb/eslint-config
ㄴ prettier // @ufb/prettier-config
ㄴ typescript // @ufb/tsconfig
각 디렉터리 상세 소개
tooling 디렉터리 — 공통 설정 패키지
모든 패키지에 공통 적용되는 설정을 한 곳에 모아 유지보수성과 일관성 확보
- @ufb/eslint-config: 프레임워크별 ESLint 규칙 분리 관리
base.js: 공통 ESLint 설정nest.js: NestJS 전용next.js: Next.js 전용react.js: React 전용
- @ufb/prettier-config: 전체 공통 Prettier 설정
- 추가 플러그인:
@ianvs/prettier-plugin-sort-imports(import 정렬),prettier-plugin-tailwindcss(Tailwind 클래스 정렬)
- 추가 플러그인:
- @ufb/tsconfig: 공유 TypeScript 설정 파일
tooling 사용 방법 (각 패키지 package.json)
{
"prettier": "@ufb/prettier-config",
"eslintConfig": {
"extends": [
"@ufb/eslint-config/base",
"@ufb/eslint-config/react"
],
"root": true
}
}
- 각 패키지에 별도 설정 파일 없이
package.json에서만 설정 가능 - ESLint/Prettier 플러그인 버전을 한 곳에서만 관리
packages 디렉터리 — 코드 공유 패키지
코드 재사용성 향상 및 관심사 분리를 위한 공유 패키지 모음
- @ufb/ui: 관리자 웹 UI 컴포넌트 (웹 패키지 전용)
- @ufb/tailwind: Tailwind CSS 설정 (관리자 웹 전용)
- @ufb/shared: API + 웹 패키지 공통 코드 공유
모듈 시스템에 따른 코드 공유 방식
JavaScript의 두 가지 모듈 시스템:
- CJS (CommonJS): NestJS(API)가 사용
- ESM (ECMAScript Modules): Next.js(웹)가 사용
- 핵심 문제: 두 모듈 시스템은 서로 호환되지 않음
| 패키지 | 사용처 | 모듈 시스템 | 빌드 여부 | 비고 |
|---|---|---|---|---|
| @ufb/ui | 웹 전용 | ESM | 빌드 불필요 | 동일 모듈 시스템 |
| @ufb/tailwind | 웹 전용 | ESM | 빌드 필요 | Turborepo 파이프라인 선행 빌드 |
| @ufb/shared | API + 웹 | CJS + ESM | 빌드 필요 | tsup으로 CJS/ESM 각각 빌드 |
빌드 방식 장단점 비교
- 빌드 불필요 (동일 모듈 시스템):
- 장점: 빌드 의존성 불필요, 전체 빌드 시간 단축
- 단점: 동일 모듈 시스템을 의도적으로 맞춰야 함
- 빌드 필요 (다른 모듈 시스템):
- 장점: 모듈 호환성 고민 없이 코드 개발에 집중 가능
- 단점: 서브 패키지 별도 빌드 시간 추가 소요
apps 디렉터리 — 애플리케이션 패키지
실제로 빌드되어 배포되는 최종 애플리케이션 패키지
- api: NestJS 기반 API 서버 (CJS 모듈 시스템)
- web: Next.js 기반 관리자 웹 (ESM 모듈 시스템)
- 다수의 서브 패키지를 임포트하므로 빌드 의존성 관리 및 빌드 최적화 필수
Turborepo를 이용한 파이프라인 및 빌드 캐시 설정
Turborepo 핵심 특징
- JavaScript/TypeScript 모노리포를 위한 고성능 빌드 시스템
- 캐시 활용: 동일 작업 중복 실행 방지
- 태스크 병렬 실행 지원
- 파이프라인으로 태스크 간 의존성 정의 및 최적화
turbo.json 파이프라인 설정 예시
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "next-env.d.ts", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"web#dev": {
"dependsOn": ["@ufb/tailwind#build", "@ufb/shared#build"]
},
"api#dev": {
"dependsOn": ["@ufb/shared#build"]
},
"lint": {
"dependsOn": ["topo"],
"outputs": ["node_modules/.cache/.eslintcache"]
},
"typecheck": {
"dependsOn": ["topo"],
"outputs": ["node_modules/.cache/tsbuildinfo.json"]
},
"test": {
"dependsOn": ["topo", "@ufb/shared#build"]
},
"format": {
"outputs": ["node_modules/.cache/.prettiercache"],
"outputMode": "new-only"
},
"clean": { "cache": false }
},
"globalDependencies": ["**/.env"]
}
topo 태스크의 역할 (핵심)
- 아무 작업도 수행하지 않는 가상 태스크
- 서브 패키지 간 의존성 토폴로지(topology) 를 생성
- 문제 상황 예시:
web패키지가ui패키지를 임포트- 전체
typecheck실행 후 →ui패키지 수정 →web만 대상으로typecheck재실행 web코드는 변경되지 않았지만, 의존하는ui가 바뀌었으므로 캐시를 그대로 쓰면 안 됨
topo태스크가 이 의존성 관계를 추적해 올바른 캐시 무효화 보장
캐시 파일 경로 설정
# 루트 package.json scripts
"format": "turbo format --continue -- --cache --cache-location='node_modules/.cache/.prettiercache'",
"lint": "turbo lint --continue -- --cache --cache-location 'node_modules/.cache/.eslintcache'",
// 각 패키지 tsconfig.json
{ "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }
globalDependencies 활용
.env파일을globalDependencies에 등록.env파일 변경 시 전체 캐시 무효화 → 환경별(개발/운영) 재캐싱 보장dev,build태스크에 특히 유용
원격 캐시 (Remote Cache)
필요성
- 로컬 캐시는 Docker 빌드 또는 CI 환경에서 사용 불가
- 여러 머신에서 동일 코드를 반복 빌드하는 비효율 발생
- 분산 계산 캐싱(Distributed Computation Caching) 으로 해결
설정 방법
- Vercel 사용 시: 별도 설정 없이 배포 시 자동 원격 캐싱
- Vercel 미사용 시: 오픈소스
turborepo-remote-cache서버를 직접 운영
// .turbo/config.json
{
"teamid": "team_myteam",
"apiurl": "http://localhost:3000"
}
# Dockerfile
ENV TURBO_TOKEN=
COPY turbo.json ./
COPY .turbo/config.json ./.turbo/
RUN --mount=type=bind,source=.git,target=.git \
pnpm turbo build
핵심 인사이트 요약
- 디렉터리 3분할 전략:
tooling(설정) →packages(공유 코드) →apps(애플리케이션) 순으로 의존 방향이 단방향으로 흐름 - 모듈 시스템 인식 필수: CJS/ESM 혼용 시 빌드 전략을 패키지별로 명확히 결정해야 함
topo태스크: Turborepo에서 빌드 없이 공유하는 내부 패키지의 의존성 캐시를 올바르게 유지하는 핵심 패턴- 캐시 파일 명시: lint/format/typecheck 모두 캐시 파일 경로를
outputs에 등록해야 Turborepo가 캐시를 재사용 가능 - 원격 캐시: CI/Docker 환경에서 캐시 효율을 얻으려면 원격 캐시 서버 구성 필수