1. 배경
현대적인 프론트엔드 아키텍처에서 모노레포(Monorepo)와 MicroFrontend(MFE)의 도입은 이제 규모 있는 프로젝트의 필수적인 선택지가 되었습니다. 특히 pnpm 환경을 채택한 우리 프로젝트는 여러 패키지가 하나의 저장소 내에 공존하며, 패키지 간의 유기적인 코드 참조가 빈번하게 발생합니다.
하지만 모노레포 설정 과정에서 마주하는 tsconfig.json의 paths와 references, 그리고 package.json의 workspace:* 설정은 언뜻 비슷해 보여 개발자에게 혼란을 주곤 합니다. "왜 이 세 가지를 동시에 설정해야 하는가?"라는 의문을 해결하지 못한 채 넘어가면, 결국 빌드 성능 저하나 타입 동기화 누락 같은 운영상의 병목 현상을 겪게 됩니다. 본 글에서는 실무적인 관점에서 각 설정의 핵심 역할과 최적의 조합 방안을 공유하고자 합니다.
2. 의존성 관리의 세 가지 축: 핵심 분석
(1) tsconfig.json - paths: 가독성을 위한 경로 별칭(Alias)
paths는 타입스크립트 컴파일러에게 특정 모듈의 물리적 위치를 대신할 별칭을 알려주는 역할을 합니다.
- 실무적 역할:
../../../../shared/src/components와 같은 복잡한 상대 경로를@shared/components와 같이 직관적인 경로로 치환하여 코드의 가독성을 높여줍니다. - 운영 시 유의사항:
paths는 어디까지나 컴파일러를 위한 '이름표'일 뿐입니다. 실제 프로젝트 간의 빌드 순서를 제어하거나 물리적인 연결을 생성하지 않습니다. 만약 번들러(Vite, Webpack 등) 설정에서 이 경로를 해석하는 플러그인이 누락된다면, 에디터상에서는 문제가 없어도 실제 실행 시점에Module Not Found에러가 발생할 수 있습니다.
(2) tsconfig.json - references: 프로젝트 간 논리적 연결
타입스크립트 3.0부터 도입된 Project References는 프로젝트 간의 의존 관계를 컴파일러가 명확히 인지하도록 선언하는 기능입니다.
- 핵심 역할: 빌드 도구(tsc)가 프로젝트의 의존성 그래프를 이해하게 하여, 각 패키지를 독립된 컴파일 단위로 격리합니다.
- 기술적 이점: 가장 큰 장점은 증분 빌드(Incremental Build)입니다. 수정이 발생한 패키지와 그 영향을 받는 패키지만 선별적으로 재빌드하므로, 전체 컴파일 속도가 비약적으로 향상됩니다. 또한
composite: true설정을 통해 선언 파일(.d.ts) 생성을 강제함으로써 패키지 간의 타입 경계를 엄격히 보호합니다.
(3) package.json - workspace:*: 물리적인 심볼릭 링크(Symbolic Link)
pnpm 워크스페이스 기능을 통해 로컬 내의 다른 패키지를 node_modules 내부로 연결하는 설정입니다.
- 핵심 역할: 로컬 패키지를 마치 외부 라이브러리처럼 인식하게 하여, 실제 물리적인 의존성 설치 과정을 자동화합니다.
- 실무적 이점:
workspace:*를 사용하면 별도의 버전 관리 없이 항상 최신 상태의 로컬 코드를 참조할 수 있습니다. 이는 패키지 매니저가 로컬 의존성 간의 순서를 파악하고 설치하는 핵심 근거가 됩니다.
3. 실제 프로젝트 설정 예시
[예시 1] 공통 타입 패키지 설정 (shared/types)
로직 없이 타입만 있는 패키지입니다. tsconfig.json 설정을 엄격하게 가져가야 합니다.
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"composite": true, // 다른 프로젝트에서 참조할 수 있도록 설정
"declaration": true, // 타입 선언 파일(.d.ts) 생성 필수
"declarationMap": true, // 소스 코드 추적을 위한 맵 파일 생성
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*"]
}
[예시 2] 기능을 담당하는 서비스 패키지 (apps/web)
위의 shared/types를 가져다 쓰는 메인 앱의 설정입니다.
1) package.json 설정
{
"name": "@apps/web",
"dependencies": {
"@shared/types": "workspace:*" // 물리적으로 연결
}
}
2) tsconfig.json 설정
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@shared/types": ["../../shared/types/src/index.ts"] // 가독성 향상
}
},
"references": [
{ "path": "../../shared/types" } // 논리적 빌드 순서 연결 (가장 중요)
]
}
4. 실무 트러블슈팅
[사례 1] 타입 변경 사항이 즉각 반영되지 않는 경우
공통 타입 패키지인 shared/types에서 인터페이스를 수정했음에도, 이를 참조하는 서비스 패키지에서 이전 타입을 그대로 인식하는 상황이 빈번합니다. 이는 대개 paths만 설정하고 references를 누락했을 때 발생합니다. 컴파일러가 프로젝트 간의 종속성을 파악하지 못해 캐시된 데이터를 참조하는 것입니다. 이때 references 설정을 통해 명시적인 빌드 관계를 선언하면 타입 업데이트가 실시간으로 전파되는 환경을 구축할 수 있습니다.
[사례 2] 빌드 서버(CI)에서의 배포 오류
로컬 환경에서는 문제가 없으나 CI 환경에서만 빌드가 실패한다면, package.json의 의존성 선언을 점검해야 합니다. workspace:* 설정이 누락된 경우, 빌드 서버는 패키지 간의 선후 관계를 파악하지 못해 런타임 오류를 야기합니다. 물리적(Workspace) 연결과 논리적(References) 연결이 병행되어야만 견고한 파이프라인 유지가 가능합니다.
5. 결론: 상호 보완을 통한 아키텍처 완성
결론적으로 모노레포의 안정성은 어느 한 가지 설정이 아닌, 세 가지 요소의 유기적인 상호 보완에서 나옵니다.
| 구분 | 관리 주체 | 핵심 목적 | 생략 시 리스크 |
|---|---|---|---|
| Workspace | pnpm | 물리적 패키지 연결 | 모듈 인식 불가 및 배포 실패 |
| References | tsc | 논리적 빌드 순서 및 타입 동기화 | 빌드 성능 저하 및 타입 불일치 |
| Paths | tsc/번들러 | 개발 편의성 및 가독성 향상 | 상대 경로 복잡도 증가 |
단순한 경로 설정에서 나아가, 인프라 수준의 정교한 의존성 관리를 수행하는 것은 개발 생산성과 코드 품질을 방어하는 가장 효율적인 투자입니다. 이러한 기반 설정을 탄탄히 다지는 과정이 결국 대규모 협업 환경에서도 흔들리지 않는 견고한 시스템을 만드는 첫걸음이 될 것입니다.
참고 문헌
- TypeScript Official Documentation - Project References
- pnpm Documentation - Workspaces