Nx Monorepo
nx-best-practices 스킬 완전 가이드.
폴더 구조, 태그와 경계, TypeScript 프로젝트 참조, 플러그인 executor, 명령어 표면.
폴더 구조, 태그와 경계, TypeScript 프로젝트 참조, 플러그인 executor, 명령어 표면.
이 페이지는
.agents/skills/monorepo/nx-best-practices/SKILL.md의 내용을 토대로 합니다. Agent에게 Nx 관련 작업을 시킬 때 이 스킬이 자동으로 로드됩니다.2계층 폴더 구조
GS Retail Nx 모노레포의 표준 레이아웃입니다. 앱과 라이브러리를 명확히 분리합니다.
workspace-root/
├── apps/ # 배포 가능한 애플리케이션
│ ├── web/ # Next.js app
│ ├── admin/ # 어드민 Next.js app
│ └── mobile/ # React Native (별도 scope)
│
├── libs/ # 공유 라이브러리 (scope/type 2계층)
│ ├── web/ # scope: web
│ │ ├── feature-checkout/ # type: feature
│ │ ├── data-access-cart/ # type: data-access
│ │ ├── ui-button/ # type: ui
│ │ └── util-format/ # type: util
│ ├── shared/ # scope: shared (cross-app)
│ │ ├── ui-design-system/
│ │ └── util-date/
│ └── admin/ # scope: admin
│
├── packages/ # npm 배포용 패키지만 (외부 게시)
│
├── nx.json
├── pnpm-workspace.yaml
└── tsconfig.base.json
타입 접미사 규칙
| 접미사 | 설명 | 예시 |
|---|---|---|
feature-* | 라우트/페이지 수준 스마트 컴포넌트. 다른 lib 조합. | feature-checkout |
data-access-* | TanStack Query 훅, API 클라이언트, Zustand 스토어 | data-access-cart |
ui-* | 프레젠테이셔널 컴포넌트 (앱 로직 없음) | ui-button |
util-* | 순수 함수 유틸리티, React 없음 | util-format |
태그와 모듈 경계
각 라이브러리의 project.json에 scope 태그와 type 태그를 반드시 붙입니다.
// libs/web/feature-checkout/project.json
{
"name": "web-feature-checkout",
"tags": ["scope:web", "type:feature"]
}
scope:web
scope:shared
scope:admin
type:feature
type:data-access
type:ui
type:util
@nx/enforce-module-boundaries 설정
ESLint 규칙으로 경계를 강제합니다. .eslintrc.json 또는 eslint.config.mjs에 적용:
{
"@nx/enforce-module-boundaries": ["error", {
"allow": [],
"depConstraints": [
// 같은 scope끼리만 의존 가능
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:web", "scope:shared"]
},
{
"sourceTag": "scope:admin",
"onlyDependOnLibsWithTags": ["scope:admin", "scope:shared"]
},
// 타입 방향성: feature → data-access → util
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": ["type:feature", "type:data-access", "type:ui", "type:util"]
},
{
"sourceTag": "type:data-access",
"onlyDependOnLibsWithTags": ["type:data-access", "type:util"]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:ui", "type:util"]
},
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
}
]
}]
}
방향성 핵심:
feature → data-access → util. 역방향 import는 lint 에러. ui는 util만 의존 가능 (feature/data-access 금지).TypeScript 프로젝트 참조
Nx 워크스페이스에서 경로 별칭(path alias) 대신 TypeScript 프로젝트 참조를 우선합니다. 증분 빌드(incremental build)를 활성화해 빌드 속도를 높입니다.
// libs/web/util-format/tsconfig.json
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"composite": true, // 프로젝트 참조 필수
"incremental": true, // 증분 빌드
"declaration": true,
"declarationMap": true
}
}
// tsconfig.base.json (workspace root)
{
"compilerOptions": {
"paths": {
"@myorg/web/util-format": ["libs/web/util-format/src/index.ts"]
}
}
}
// 프로젝트 참조 추가 (tsconfig.json 참조 배열)
{
"references": [
{ "path": "../../../libs/web/util-format" }
]
}
경로 별칭(
@myorg/*)은 IDE/런타임에, TS 프로젝트 참조는 tsc --build에 활용됩니다. 둘 다 설정하되 참조를 정규 방식으로 사용하세요.플러그인 Executor
규칙: 프레임워크 네이티브 executor(@nx/next, @nx/react)를 원시 nx:run-commands보다 우선합니다.
// apps/web/project.json — 올바른 방식
{
"targets": {
"build": {
"executor": "@nx/next:build", // ✅ 프레임워크 플러그인
"options": { "outputPath": "dist/apps/web" }
},
"serve": {
"executor": "@nx/next:server", // ✅
"options": { "buildTarget": "web:build" }
}
}
}
// ❌ 피해야 할 방식
{
"targets": {
"build": {
"executor": "nx:run-commands",
"options": { "commands": ["next build"] } // raw command
}
}
}
nx:run-commands는 Nx가 캐시 키를 올바르게 계산할 수 없어 빌드 캐시 효율이 떨어집니다.
명령어 표면
| 명령어 | 설명 |
|---|---|
nx graph | 프로젝트 의존성 그래프 시각화 (브라우저 열림) |
nx affected -t build | 변경된 프로젝트와 그 영향 받는 프로젝트만 빌드 |
nx affected -t test | 변경된 프로젝트만 테스트 |
nx affected -t lint | 변경된 프로젝트만 lint |
nx run-many -t build | 모든 프로젝트 빌드 (병렬) |
nx run-many -t lint test | 모든 프로젝트 lint + test |
nx g @nx/next:app web | 새 Next.js 앱 생성 |
nx g @nx/react:lib web/ui-button | 새 React 라이브러리 생성 |
nx migrate latest | Nx 버전 업그레이드 (마이그레이션 자동 실행) |
nx reset | 캐시 초기화 (빌드 이상할 때) |
nx show project web --web | 특정 프로젝트의 targets 상세 보기 |
affected 활용 팁
# PR 기준으로 affected 계산
nx affected -t build --base=main --head=HEAD
# 특정 브랜치 대비
nx affected -t test --base=origin/main
# verbose 출력
nx affected -t build --verbose
안티패턴 체크리스트
- ✗ 내부 파일 직접 import:
import from '@myorg/lib/src/components/Foo'→ 반드시index.ts를 통해 공개된 API만 사용 - ✗ 순환 의존성: A→B→A 구조.
nx graph로 주기적 확인 - ✗ 태그 없는 라이브러리:
project.json에 tags 없으면 경계 규칙이 적용되지 않음 - ✗ apps/ 에서 apps/ 직접 import: 앱 간 공유는 반드시 libs/shared/ 경유
- ✗ packages/ 에 내부 전용 코드: packages/는 외부 npm 배포 전용
- ✗ nx:run-commands 남발: 프레임워크 플러그인이 있는데 raw command 사용 금지
- ✗ 모든 것을 feature lib으로: 유틸리티는 util, 스토어는 data-access로 분리
- ✗ 경계 위반 무시: ESLint 경고를
// eslint-disable로 무력화 금지
Claude와 Nx 작업 플로우
Agent에게 Nx 관련 작업을 요청할 때 nx-best-practices 스킬이 자동 로드됩니다.
# 새 feature lib 생성 요청 예시
"웹 앱에 장바구니 feature lib 만들어줘"
→ Agent가 nx g @nx/react:lib web/feature-cart 실행
→ project.json에 tags: ["scope:web", "type:feature"] 자동 추가
→ tsconfig.base.json 경로 별칭 등록
→ enforce-module-boundaries 준수 확인
nx-best-practices 스킬 트리거 조건: nx.json이 있는 워크스페이스 작업, 새 app/lib 스캐폴딩, module boundary 위반/감사, Nx 버전 업그레이드, project.json/tsconfig 변경 시 자동 로드됩니다.