노트

Nx

Nx

인프라#tooling#ci · 연결된 개념 7개

쉽게 말하면

Nx는 큰 모노레포를 맡는 관리 회사 같아요. 바뀐 곳에 영향받는 프로젝트만 골라 돌려 CI를 줄이고, 새 라이브러리를 같은 양식으로 찍어 내고, '이 기능은 저 기능을 가져다 쓰지 마' 같은 규칙까지 지키게 해요.

비유의 한계 일을 많이 맡기는 만큼 도입 비용이 들어요. 프로젝트에 어떤 태그를 붙이고 어떤 경계 규칙을 강제할지 먼저 정해야 해서, 준비가 안 됐다면 Turborepo로 가볍게 시작하고 나중에 옮기는 경우가 흔해요.

Nx는 캐싱과 병렬 실행에 더해 프로젝트 그래프, 코드 생성, 아키텍처 규칙 강제까지 제공하는 모노레포 플랫폼이다. "빠른 실행 한 가지"에 집중하는 Turborepo와 달리 기능을 두루 갖춘 쪽을 지향한다.

태스크 실행과 프로젝트 그래프

  • 태스크의 출처는 셋이다. package.json 스크립트, project.json의 targets, 그리고 플러그인이 설정 파일에서 추론한 태스크(예: @nx/vite가 vite.config.ts를 보고 build·typecheck를 만들고 캐시 설정까지 붙인다. Vitest의 test는 Nx 22부터 @nx/vitest 몫이다)
  • 프로젝트 사이 의존은 import를 분석해 프로젝트 그래프(project graph)로 만든다(nx graph). 순서는 nx.json의 targetDefaults에 "build": { "dependsOn": ["^build"] }처럼 적는다. ^는 "의존하는 프로젝트의 같은 태스크 먼저"라는 뜻이다
  • nx run-many -t build test는 전체 또는 -p로 고른 프로젝트에서 여러 태스크를 그래프 순서를 지키며 병렬로 돌린다

affected: 바뀐 것과 그 의존자만

nx affected -t test --base=origin/main --head=HEAD
nx graph --affected
  • git으로 바뀐 파일을 찾고, 그 파일이 속한 프로젝트와 그 프로젝트에 의존하는 모든 프로젝트를 affected로 표시한다. lib10이 바뀌면 lib10을 쓰는 lib4, lib5, app2까지 돌고 무관한 app1은 건너뛴다
  • 기본 base는 main, head는 현재 파일시스템이다. CI에서는 "main에서 마지막으로 성공한 커밋"을 base로 두는 것을 권한다. GitHub Actions는 nrwl/nx-set-shas가 NX_BASE·NX_HEAD를 채워 준다. base 커밋이 있어야 하므로 fetch-depth: 0이 필요하다
  • lockfile이 바뀌면 기본으로 모든 프로젝트가 affected다(projectsAffectedByDependencyUpdates: "all"). "auto"로 두면 실제로 의존성이 바뀐 프로젝트만 고른다

캐시: namedInputs로 입력을 이름 붙여 쓴다

{
  "namedInputs": {
    "default": ["{projectRoot}/**/*"],
    "production": ["default", "!{projectRoot}/**/*.spec.ts", { "env": "NODE_ENV" }, { "runtime": "node --version" }]
  },
  "targetDefaults": {
    "build": { "cache": true, "inputs": ["production", "^production"], "outputs": ["{workspaceRoot}/dist/{projectRoot}"] }
  }
}
  • 입력은 파일 패턴만이 아니라 환경변수({ "env": ... })와 런타임 명령 결과({ "runtime": "node --version" })도 된다. 테스트 파일을 production에서 빼면 테스트만 고쳐도 build 캐시가 유지된다
  • ^production은 의존하는 프로젝트의 production 입력까지 해시에 넣는다는 뜻이다
  • dist·build 출력 폴더는 자동으로 인식한다. 다른 곳이면 outputs에 적는다. 히트(hit)면 터미널 출력과 출력 파일을 복원한다
  • 원격 캐시는 Nx Cloud(nx connect)로 공유한다. 캐시는 부수 효과가 없는 태스크에만 안전하다. 라이브 백엔드를 치는 e2e는 캐시하지 않는다. 미스 원인은 nx show project <이름> --web으로 실제 적용된 inputs를 보고 좁힌다

생성기와 마이그레이션

  • nx g @nx/react:lib packages/mylib처럼 플러그인 생성기로 프로젝트를 만든다. 폴더 구조, 린트·테스트 설정, 태그가 같은 양식으로 찍혀 나온다
  • 조직 규약은 커스텀 생성기로 코드에 담는다. 생성기는 가상 파일시스템 Tree와 옵션 스키마를 받는 함수이고, @nx/devkit의 formatFiles·installPackagesTask 같은 도우미를 쓴다. 생성기는 사람이 기억하는 컨벤션을 실행 가능한 코드로 바꾸는 장치다
  • nx migrate는 Nx와 플러그인 버전을 올리면서 설정·코드 변경까지 함께 적용한다

모듈 경계 규칙

@nx/enforce-module-boundaries ESLint 규칙이 프로젝트 태그로 import 방향을 강제한다.

{
  "@nx/enforce-module-boundaries": ["error", {
    "depConstraints": [
      { "sourceTag": "scope:client", "onlyDependOnLibsWithTags": ["scope:client", "scope:shared"] },
      { "sourceTag": "type:feature", "notDependOnLibsWithTags": ["type:app"] }
    ]
  }]
}
  • 태그는 project.json의 tags나 package.json의 nx.tags에 적는다. scope:*(도메인)와 type:*(계층) 두 축으로 붙이는 관례가 흔하다. 태그 매칭은 정확한 문자열, 글로브(scope:*), 정규식(/^scope.*/)을 받는다
  • sourceTag가 붙은 프로젝트는 onlyDependOnLibsWithTags에 있는 태그의 프로젝트만 import할 수 있고, notDependOnLibsWithTags에 있는 것은 import하지 못한다. 태그 없는 프로젝트는 기본으로 아무것도 import하지 못한다
  • 이 규칙은 순환 의존(자기 자신 포함), lazy-load하는 라이브러리의 정적 import, package.json에 없는 전이 의존성 import(banTransitiveDependencies), 특정 npm 패키지 금지·허용(bannedExternalImports·allowedExternalImports), buildable 라이브러리가 non-buildable을 import하는 것(enforceBuildableLibDependency)도 잡는다
  • 위반은 린트 에러로 나온다. "scope:admin 프로젝트는 scope:shared나 scope:admin만 의존할 수 있다" 같은 메시지다

고르는 기준

  • 코드 생성, affected 분석, 아키텍처 거버넌스, 다중 언어가 필요한 큰 조직이라면 Nx. Lerna도 지금은 패키지 탐지와 태스크 실행을 Nx에 맡긴다
  • 그만큼 도입 비용이 든다. 프로젝트를 어떻게 태깅하고 어떤 규칙을 강제할지 정해야 한다. 그럴 준비가 안 됐다면 Turborepo로 가볍게 시작하고 팀이 커지면 옮기는 순서가 흔하다(2026 기준)

모듈 경계 강제는 사람이 기억하지 않아도 결합도 규칙이 지켜지게 하는 장치다. 폴더 규약으로 같은 것을 노리는 Feature-Sliced Design은 도구 없이는 유지되기 어렵다는 점에서 대비된다. 팀 구조와 코드 구조를 맞추는 일은 콘웨이의 법칙와 이어지고, 모노레포 일반은 모노레포를 본다.

출처: Nx: Run Tasks · Nx with Vite · Run Only Tasks Affected by a PR · Cache Task Results · Caching tutorial — namedInputs · Generate Code · Automate Updating Dependencies · Enforce Module Boundaries · enforce-module-boundaries 옵션 · Mastering the Project Boundaries in Nx · Lerna and Nx

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • NestJS

    NestJS는 TypeScript를 전제로 모듈·의존성 주입(Dependency Injection, DI)·데코레이터(decorator) 구조를 제공하는 Node.js 서버 프레임워크다. 2017년 Kamil Myśliwiec가 만들었고, 구조를 강제하는 방식 때문에 흔히 "Node.js의 Spring"이라 불린다.

  • loading 경계와 레이아웃 끌어올리기

    Next.js App Router에서 loading.tsx는 같은 세그먼트의 page와 그 아래(하위 layout 포함)를 suspense로 감싸지만, **같은 세그먼트의 layout은 감싸지 않는다**. 그래서 데이터를 기다릴 필요가 없는 공통 UI(탭바, 필터 헤더)를 page에서 layout으로 끌어올리면 스켈레톤에 덮이지 않고 처음부터 실제 모습으로 보인다.

  • 배럴 파일과 re-export

    배럴 파일(Barrel File)은 여러 모듈의 export를 index.ts 하나에 모아 다시 내보내(re-export) 진입점을 하나로 만드는 파일이다. 쓰는 쪽은 import { Button, Input } from './components'처럼 한 경로에서 가져온다.

  • CPU 아키텍처와 네이티브 모듈

    CPU 아키텍처는 CPU가 실행하는 명령어 집합(Instruction Set Architecture, ISA)의 종류다. 서버와 PC에서는 x86-64와 ARM64 두 계열이 주로 쓰이고, 한 계열용으로 컴파일된 기계어는 다른 계열 CPU에서 실행되지 않는다. Node.js 프로젝트에서는 이 차이가 네이티브 모듈에서 드러난다.

  • tsconfig 핵심 옵션

    tsconfig.json은 TypeScript 컴파일러(tsc)에게 어떤 파일을, 어떤 규칙으로 검사하고, 무엇을 출력할지 알려 주는 프로젝트 설정 파일이다. tsc --init으로 만들고, 이 파일이 있으면 tsc만 쳐도 프로젝트 전체를 컴파일한다(-w는 감시 모드, watch mode).

보기 옵션