테마
NX: 스마트하고 확장 가능한 빌드 시스템
NX는 프로젝트 그래프 분석, 스마트 캐싱, 분산 태스크 실행, 코드 생성 등 가장 풍부한 기능을 제공하는 고급 모노레포 빌드 시스템이다.
학습 목표
- NX의 핵심 철학(스마트, 빠름, 확장 가능)을 이해한다
- NX의 주요 기능(태스크 실행, 캐싱, 분산 실행, 코드 생성)을 설명할 수 있다
- 프로젝트 그래프가 빌드 최적화에 어떻게 활용되는지 파악한다
- "affected" 개념을 이해하고 CI/CD에서의 활용을 설명할 수 있다
- nx.json 설정의 주요 항목을 이해한다
1. NX 소개
NX는 **"스마트하고(Smart), 빠르고(Fast), 확장 가능한(Extensible) 빌드 시스템"**을 표방한다. Nrwl이라는 회사가 개발하고 유지 관리하며, 현재 모노레포 빌드 시스템 도구 중 가장 많은 기능을 제공하는 도구다.
NX가 신경 쓰는 세 가지 영역:
| 영역 | 설명 |
|---|---|
| 개발자 생산성 향상 | 캐싱, 병렬 실행, affected 명령으로 불필요한 작업을 제거한다 |
| CI 성능 최적화 | 리모트 캐싱, 분산 태스크 실행으로 CI 파이프라인을 가속한다 |
| 코드 품질 유지 | 모듈 경계 강제, 의존 관계 분석으로 아키텍처 일관성을 보장한다 |
NX는 오픈소스이면서, NX Cloud라는 유료 상품을 통해 원격 캐싱 및 분산 태스크 실행 같은 엔터프라이즈 기능을 제공한다. 오픈소스 프로젝트에는 NX Cloud를 무료로 제공한다.
2. NX의 핵심 기능
2.1 태스크 실행
NX의 태스크 실행 명령은 다른 도구와 문법이 약간 다르다. "무엇을(스크립트) 어디서(패키지)" 순서로 지정한다.
bash
# 단일 패키지 태스크 실행: nx <script> <package>
nx build my-app
nx test my-utils
# 전체 패키지 태스크 실행
nx run-many -t build
nx run-many -t test
# 여러 태스크를 동시에 전체 실행
nx run-many -t lint,test,build
# 옵션 전달
nx build my-app --configuration=productionNX는 태스크 실행 시 자동으로 패키지 간 의존 관계를 분석하여 올바른 순서로, 가능한 병렬로 실행한다.
2.2 로컬 캐싱
NX의 캐싱은 **입력(inputs)**의 해시를 기반으로 동작한다.
캐싱의 동작 원리:
- 태스크 실행 전, 해당 패키지의 입력(소스 파일, 설정 파일, 의존 패키지의 출력 등)을 해시로 계산한다.
- 동일한 해시의 캐시가 존재하면, 실제 실행 없이 캐시된 출력물을 복원한다.
- 캐시가 없으면 태스크를 실행하고, 결과를 캐시에 저장한다.
bash
# 첫 번째 실행: 실제 빌드 수행 (예: 4.8초)
nx run-many -t build
# 두 번째 실행: 캐시 히트 (예: 0.04초)
nx run-many -t build캐싱 설정은 nx.json에서 어떤 태스크를 캐싱할지, 어떤 출력물을 저장할지 정의한다.
2.3 프로젝트 그래프
NX의 가장 강력한 기능 중 하나는 인터랙티브 프로젝트 그래프다.
bash
# 브라우저에서 프로젝트 그래프 열기
nx graph이 명령을 실행하면 브라우저에서 모든 패키지의 의존 관계를 시각적으로 확인할 수 있다. 그래프에서는:
- 각 패키지가 노드로 표시된다
- 의존 관계가 방향 있는 엣지로 연결된다
- 특정 패키지를 클릭하면 해당 패키지의 의존 관계만 필터링된다
- 드래그, 줌, 패닝 등 인터랙티브 조작이 가능하다
프로젝트 그래프는 단순한 시각화를 넘어, NX의 모든 최적화 기능의 기반이 된다. 캐싱, affected 계산, 태스크 순서 결정 모두 이 그래프를 기반으로 동작한다.
2.4 Affected: 영향받는 프로젝트만 실행
nx affected 명령은 git에서 변경된 코드를 분석하여, 실제로 영향받는 프로젝트에 대해서만 태스크를 실행한다.
bash
# 변경으로 영향받는 프로젝트만 빌드
nx affected -t build
# 변경으로 영향받는 프로젝트만 테스트
nx affected -t test
# 기준 브랜치 지정
nx affected -t build --base=mainAffected 계산 과정:
CI/CD에서의 활용:
nx affected는 CI 파이프라인에서 특히 유용하다. PR이 올라왔을 때 전체 모노레포를 빌드/테스트하는 대신, 변경으로 영향받는 프로젝트만 처리하므로 CI 시간과 비용을 대폭 절감할 수 있다.
3. 리모트 캐싱과 분산 태스크 실행
3.1 리모트 캐싱
로컬 캐싱은 개인 머신에서만 유효하다. 리모트 캐싱은 캐시를 클라우드에 저장하여 팀 전체와 CI가 공유할 수 있게 한다.
bash
# NX Cloud에 연결
nx connect-to-nx-cloud리모트 캐싱이 활성화되면:
- 개발자 A가 빌드한 결과가 클라우드에 캐시된다.
- 개발자 B가 같은 코드를 빌드하면 클라우드 캐시를 다운로드하여 즉시 완료된다.
- CI에서 빌드할 때도 개발자들이 이미 빌드한 캐시를 재사용한다.
3.2 분산 태스크 실행 (DTE)
레포지토리의 핵심 코드가 수정되어 모든 패키지를 빌드/테스트해야 하는 경우, 하나의 CI 에이전트로는 시간이 오래 걸린다. NX Cloud의 **DTE(Distributed Task Execution)**는 여러 CI 에이전트에 태스크를 지능적으로 분배한다.
- NX가 프로젝트 그래프와 태스크 의존 관계를 분석한다.
- 병렬 실행 가능한 태스크를 여러 에이전트에 분산한다.
- 의존 관계가 있는 태스크는 순서를 보장하면서 분산한다.
- 전체 CI 파이프라인 시간을 에이전트 수에 비례하여 단축한다.
4. 플러그인 시스템과 코드 생성
NX가 **"확장 가능한(Extensible)"**이라고 자칭하는 이유는 플러그인 시스템 때문이다.
플러그인이 제공하는 두 가지 요소:
| 요소 | 설명 | 예시 |
|---|---|---|
| 생성기(Generator) | 보일러플레이트 코드를 자동 생성 | nx g @nx/react:app my-app |
| 실행기(Executor) | 특정 프레임워크에 최적화된 빌드/서빙/테스트 실행 | React, Angular, Next.js, Node.js 등 |
주요 공식 플러그인:
@nx/react- React 앱/라이브러리 생성 및 빌드@nx/angular- Angular 앱/라이브러리 생성 및 빌드@nx/next- Next.js 앱 생성 및 빌드@nx/node- Node.js 앱/라이브러리 생성 및 빌드@nx/jest- Jest 테스트 실행기@nx/cypress- Cypress E2E 테스트 실행기@nx/storybook- Storybook 통합
커뮤니티 플러그인도 활발하게 개발되고 있으며, 직접 커스텀 플러그인을 작성하여 조직의 특수한 요구사항에 맞출 수도 있다.
5. 모듈 경계 강제
NX는 패키지 간 접근 규칙을 린트 수준에서 강제하는 기능을 제공한다.
예를 들어:
feature-*태그의 라이브러리는util-*태그의 라이브러리만 의존할 수 있다.app-*은 어디든 의존 가능하지만, 다른app-*에는 의존할 수 없다.shared-*는 다른shared-*와util-*에만 의존할 수 있다.
이러한 규칙을 ESLint 설정으로 선언하면, 규칙을 위반하는 import 문이 작성될 때 빌드 전에 린트 단계에서 오류가 발생한다. 대규모 모노레포에서 아키텍처의 일관성을 유지하는 데 매우 유용하다.
6. nx.json 설정
NX 프로젝트의 루트에는 nx.json 설정 파일이 존재한다. 이 파일은 캐싱, 태스크 파이프라인, 기본 설정 등을 정의한다.
json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"outputs": ["{projectRoot}/dist"],
"cache": true
},
"test": {
"inputs": ["default", "^production"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s"
],
"sharedGlobals": []
},
"defaultBase": "main"
}주요 설정 항목 해설:
| 항목 | 설명 |
|---|---|
targetDefaults | 각 태스크(build, test, lint 등)의 기본 설정 |
dependsOn: ["^build"] | 이 태스크 실행 전에 의존 패키지의 build가 먼저 완료되어야 한다 |
inputs | 캐시 키를 구성하는 입력 파일/패턴. 이 파일들이 변경되면 캐시가 무효화된다 |
outputs | 캐싱할 출력물의 경로 |
cache: true | 이 태스크의 결과를 캐싱할지 여부 |
namedInputs | 재사용 가능한 입력 패턴의 이름 정의 |
defaultBase | affected 명령의 기준 브랜치 |
dependsOn에서 ^ 접두사는 **의존하는 프로젝트(upstream)**를 의미한다. "^build"는 "나에게 의존성으로 연결된 프로젝트들의 build가 먼저 실행되어야 한다"는 뜻이다.
7. NX 프로젝트 설정 방식
NX는 프로젝트별 설정을 두 가지 방식으로 할 수 있다.
7.1 project.json 방식
각 프로젝트 디렉토리에 project.json 파일을 두어 태스크를 정의한다.
json
{
"name": "my-app",
"targets": {
"build": {
"executor": "@nx/next:build",
"outputs": ["{projectRoot}/.next"],
"options": {
"outputPath": "dist/apps/my-app"
}
},
"serve": {
"executor": "@nx/next:server",
"options": {
"buildTarget": "my-app:build"
}
}
}
}7.2 package.json 방식
기존 package.json의 scripts 필드를 그대로 활용하면서, NX 관련 설정만 nx 키에 추가한다.
json
{
"name": "my-app",
"scripts": {
"build": "next build",
"dev": "next dev"
},
"nx": {
"targets": {
"build": {
"outputs": ["{projectRoot}/.next"]
}
}
}
}package.json 방식은 기존 프로젝트에 NX를 점진적으로 도입할 때 유리하다. NX 전용 설정을 최소화하면서도 캐싱과 태스크 오케스트레이션의 이점을 얻을 수 있다.
8. NX를 선택해야 하는 경우
NX가 적합한 프로젝트:
- 패키지 간 의존 관계가 복잡하고, 정교한 빌드 순서 관리가 필요한 경우
- CI/CD 파이프라인 최적화가 중요한 대규모 프로젝트
- 코드 생성, 모듈 경계 강제 등 아키텍처 거버넌스 기능이 필요한 경우
- React, Angular, Next.js 등 특정 프레임워크의 공식 플러그인을 활용하고 싶은 경우
- 분산 태스크 실행으로 CI 시간을 극단적으로 줄이고 싶은 경우
NX의 단점:
- 기능이 많은 만큼 학습 곡선이 높다
- 설정이 세밀한 만큼 복잡해질 수 있다
- NX Cloud 의존 시 비용이 발생한다 (오픈소스 무료)
핵심 정리
| 핵심 개념 | 요약 |
|---|---|
| NX의 철학 | 스마트하고, 빠르고, 확장 가능한 빌드 시스템 |
| 프로젝트 그래프 | 패키지 간 의존 관계를 DAG로 분석. 캐싱, affected, 순서 결정의 기반 |
| Affected | git diff를 기반으로 영향받는 프로젝트만 선택적으로 빌드/테스트한다 |
| 캐싱 | 입력 해시 기반 로컬 캐싱 + NX Cloud 리모트 캐싱으로 빌드 시간을 극적으로 단축 |
| 분산 태스크 실행 | CI 에이전트에 태스크를 지능적으로 분배하여 CI 시간을 병렬화한다 |
| 플러그인 시스템 | 생성기와 실행기를 통해 다양한 프레임워크를 지원하고 확장할 수 있다 |
| 모듈 경계 | 패키지 간 접근 규칙을 린트 수준에서 강제하여 아키텍처 일관성을 유지한다 |
다음 단계
이번 장에서는 NX의 철학, 핵심 기능, 설정 방식을 전반적으로 살펴보았다. 다음 장에서는 Microsoft가 개발한 대규모 모노레포 관리 도구 Rush에 대해 학습하고, NX와는 다른 접근 방식(정책 관리, pnpm 기반 팬텀 의존성 제거 등)을 이해한다.