재하의 개발 블로그
아티클 12 분 소요

Volta vs NVM — Node.js 버전 관리 도구 비교

nodejs volta nvm tooling devops developer-experience

프로젝트에서 Git hooks를 설정하다가 이상한 문제를 만났다. 모든 검사는 로컬에서 통과하는데, git commit만 하면 exit code 3으로 실패하는 것이다.

husky - pre-commit script failed (code 3)

원인을 찾아보니 NVM(Node Version Manager)이 문제였다. 더 정확히는 NVM의 동작 방식이 Git hooks의 sh -e 모드와 충돌한 것이다.

이 문제를 해결하며 Volta라는 대안을 알게 되었고, 마이그레이션 후 문제가 해결되었다. 이 글에서는 NVM과 Volta의 차이점, 각 도구의 장단점, 그리고 적합한 사용 사례를 정리한다.


NVM이란 무엇인가?

NVM(Node Version Manager)은 Node.js 버전을 관리하는 가장 유명한 도구다.

# NVM 설치
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
 
# Node.js 설치
nvm install 22
nvm use 22
 
# 프로젝트별 버전 지정
echo "22" > .nvmrc
nvm use  # .nvmrc 읽어서 버전 전환

NVM의 동작 방식

NVM은 쉘 함수로 구현되어 있다. nvm use를 실행하면 현재 쉘의 PATH 환경변수를 직접 수정한다.

# nvm use 실행 전
echo $PATH
# /usr/local/bin:/usr/bin:/bin
 
# nvm use 22 실행 후
echo $PATH
# /Users/username/.nvm/versions/node/v22.0.0/bin:/usr/local/bin:/usr/bin:/bin

NVM의 장점

  1. 성숙한 생태계: 10년 이상 운영되며 검증된 도구
  2. 광범위한 호환성: Unix 계열 시스템에서 안정적으로 동작
  3. 단순한 설계: 쉘 함수 기반이라 동작 방식을 이해하기 쉬움
  4. 큰 커뮤니티: 문제 발생 시 해결 방법을 쉽게 찾을 수 있음
  5. 유연한 버전 선택: nvm install node (최신), nvm install --lts (LTS) 등 다양한 옵션

NVM의 한계

  1. 쉘 초기화 필요: .bashrc, .zshrc에 NVM 초기화 코드를 추가해야 함
  2. 쉘 시작 지연: 매번 쉘을 열 때마다 NVM을 로드하므로 느림 (0.15~0.20초)
  3. 환경 의존성: 현재 쉘 세션에만 영향을 미침
  4. 수동 전환 필요: 프로젝트마다 nvm use를 실행해야 함

Volta는 무엇이 다른가?

Volta는 Rust로 작성된 Node.js 버전 관리 도구다.

# Volta 설치
curl https://get.volta.sh | bash
 
# Node.js 설치
volta install node@22
volta install pnpm
 
# 프로젝트별 버전 고정
volta pin node@22
volta pin pnpm@10

Volta의 핵심 차이점

Volta는 NVM과 근본적으로 다른 방식으로 동작한다.

1. 바이너리 심링크 + PATH 관리

Volta는 쉘 함수가 아니라 **실제 실행 파일(shim)**을 사용한다.

which node
# /Users/username/.volta/bin/node
 
ls -la ~/.volta/bin/node
# lrwxr-xr-x  1 username  staff  34 Mar 19 00:00 /Users/username/.volta/bin/node -> ../volta
  • ~/.volta/binPATH한 번만 추가하면 끝
  • 쉘을 열 때마다 NVM 초기화할 필요 없음
  • volta라는 단일 바이너리가 모든 명령어를 가로챔

2. 프로젝트별 자동 전환

NVM과의 주요 차이점은 자동 버전 전환이다.

// package.json
{
  "volta": {
    "node": "22.12.0",
    "pnpm": "10.32.1"
  }
}

프로젝트 디렉토리로 이동하면 자동으로 올바른 버전을 사용한다. nvm use를 실행할 필요가 없다.

cd /path/to/project-with-node-22
node --version  # v22.12.0 (자동 전환)
 
cd /path/to/another-project-with-node-20
node --version  # v20.0.0 (자동 전환)

3. NPM 패키지 버전 관리

Volta는 Node.js뿐만 아니라 글로벌 NPM 패키지의 버전도 관리한다.

# 프로젝트별로 다른 TypeScript 버전 사용
cd project-a
volta install typescript@4.9
tsc --version  # 4.9.x
 
cd project-b
volta install typescript@5.0
tsc --version  # 5.0.x

NVM에서는 글로벌 패키지가 Node 버전과 함께 설치되므로, Node 버전을 바꾸면 글로벌 패키지를 다시 설치해야 한다.


실제로 겪은 문제 — NVM + Git Hooks

내가 겪은 문제는 이랬다:

# .husky/pre-commit
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 2>/dev/null
 
pnpm exec lint-staged
pnpm typecheck

이 스크립트는 로컬에서는 잘 동작했다. 하지만 git commit을 실행하면 exit code 3으로 실패했다.

문제의 원인

Husky는 Git hooks를 sh -e 모드로 실행한다. 이 모드에서는 non-zero exit code를 반환하면 즉시 종료된다.

# NVM의 nvm use는 exit code 3을 반환
# (이미 사용 중인 버전일 때)
nvm use 2>/dev/null
echo $?  # 3
 
# sh -e 모드에서는 이것이 에러로 취급됨
sh -e -c "nvm use 2>/dev/null; echo 'done'"
# (스크립트 즉시 종료, 'done' 출력 안 됨)

nvm use가 exit code 3을 반환하는 것은 에러가 아니다. "요청한 버전이 이미 활성화되어 있다"는 의미일 뿐이다.

하지만 sh -e는 이를 에러로 취급하고 스크립트를 중단한다.

|| true로도 해결 안 됨

처음에는 || true를 붙여봤다.

nvm use 2>/dev/null || true

하지만 여전히 실패했다. NVM 스크립트 내부 어딘가에서 exit 3을 직접 호출하기 때문이다.

Volta를 사용한 해결 방법

Git hooks에서 NVM 초기화를 완전히 제거하고 Volta를 사용하니 문제가 해결되었다.

# .husky/pre-commit (after)
pnpm exec lint-staged
pnpm typecheck

Volta는 PATH에 한 번만 추가하면 되므로, Git hooks에서 별도의 초기화가 필요 없다.


성능 비교

쉘 시작 시간

# NVM
time zsh -c "exit"
# 0.15s ~ 0.20s
 
# Volta
time zsh -c "exit"
# 0.03s ~ 0.05s

Volta는 쉘 초기화 오버헤드가 거의 없다.

버전 전환 시간

# NVM
time nvm use 22
# 0.5s ~ 1.0s
 
# Volta (자동 전환)
time cd project && node --version
# 즉시 (오버헤드 없음)

Volta는 디렉토리 이동만으로 버전이 전환된다.

Volta의 장점

  1. 빠른 성능: 쉘 초기화 오버헤드 없음 (0.03~0.05초)
  2. 자동 전환: 프로젝트 디렉토리 이동만으로 버전 자동 전환
  3. 패키지 관리: Node뿐만 아니라 pnpm, yarn 같은 도구도 관리
  4. 환경 안정성: Git hooks, CI 환경에서 추가 설정 불필요
  5. 프로젝트 격리: 글로벌 패키지도 프로젝트별로 버전 관리 가능

Volta의 한계

  1. 상대적으로 작은 커뮤니티: NVM 대비 사용자 수가 적어 문제 해결 사례가 제한적
  2. Windows 지원 미흡: Windows 네이티브 지원은 실험적 단계 (WSL은 정상 동작)
  3. 오래된 Node 버전 제한: 매우 오래된 레거시 버전(Node 6, 8 등) 지원 부족
  4. 팀 전환 비용: 기존 NVM 워크플로우를 사용하는 팀의 경우 마이그레이션 노력 필요
  5. 디버깅 복잡도: Shim 기반 구조로 인해 문제 발생 시 원인 파악이 어려울 수 있음

마이그레이션 가이드

1. Volta 설치

curl https://get.volta.sh | bash

2. 현재 Node 버전 확인

node --version  # v22.12.0
pnpm --version  # 10.32.1

3. Volta로 동일 버전 설치

volta install node@22.12.0
volta install pnpm@10.32.1

4. 프로젝트에 버전 고정

cd /path/to/your/project
volta pin node@22.12.0
volta pin pnpm@10.32.1

이 명령은 package.json에 자동으로 추가한다:

{
  "volta": {
    "node": "22.12.0",
    "pnpm": "10.32.1"
  }
}

5. .nvmrc 제거 (선택)

Volta를 팀 표준으로 정했다면:

rm .nvmrc
git add .nvmrc package.json
git commit -m "chore: migrate from NVM to Volta"

NVM 사용자와 공존하려면 둘 다 유지해도 된다.

6. Git hooks 정리

# .husky/pre-commit, .husky/pre-push에서
# NVM 초기화 코드 제거
 
# Before
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 2>/dev/null
 
pnpm lint
 
# After
pnpm lint  # Volta가 자동으로 올바른 버전 사용

비교 요약

항목NVMVolta
성능느림 (쉘 초기화 0.15~0.20초)빠름 (쉘 초기화 0.03~0.05초)
자동 전환❌ (수동으로 nvm use 필요)✅ (디렉토리 이동 시 자동)
커뮤니티✅ 매우 큼 (10년+ 운영)⚠️ 상대적으로 작음
Windows 지원⚠️ nvm-windows 별도 도구⚠️ 실험적 (WSL은 정상)
패키지 관리❌ (Node 버전별로 재설치)✅ (프로젝트별 격리)
Git Hooks 호환⚠️ 추가 설정 필요, 이슈 가능성✅ 추가 설정 불필요
레거시 버전✅ Node 0.x부터 지원⚠️ 오래된 버전 제한적
학습 곡선✅ 단순한 쉘 함수⚠️ Shim 구조 이해 필요

어떤 도구를 선택할까?

NVM이 적합한 경우

  1. Windows 네이티브 환경 WSL 없이 Windows에서 직접 사용해야 하는 경우

  2. 레거시 프로젝트 유지보수 Node 6, 8 같은 매우 오래된 버전이 필요한 경우

  3. 기존 팀 워크플로우 팀 전체가 NVM을 사용 중이고 전환 비용이 큰 경우

  4. 검증된 안정성 우선 오랜 기간 검증된 도구를 선호하는 보수적인 환경

Volta가 적합한 경우

  1. 새 프로젝트 시작 처음부터 효율적인 워크플로우를 구축하고 싶은 경우

  2. 여러 프로젝트 작업 프로젝트 간 빈번한 전환이 필요한 경우

  3. CI/CD 환경 최적화 Git hooks, CI 파이프라인에서 안정적인 동작이 중요한 경우

  4. 패키지 관리 복잡도 글로벌 패키지를 프로젝트별로 관리해야 하는 경우

  5. 성능 민감도 쉘 시작 시간이나 전환 속도가 중요한 경우


결론

NVM과 Volta는 같은 문제를 해결하지만 근본적으로 다른 접근법을 사용한다.

NVM은 10년 이상 검증된 안정성과 큰 커뮤니티를 가진 표준적인 선택이다. 반면 Volta는 자동 전환과 성능 최적화로 현대적인 워크플로우를 제공한다.

내가 겪은 경험

Git hooks에서 발생한 NVM의 exit code 이슈는 쉘 함수 기반 설계의 한계를 보여줬다. Volta로 전환 후 추가 설정 없이 문제가 해결되었고, 프로젝트 간 전환도 자동화되었다.

하지만 이것이 모든 상황에서 Volta가 정답이라는 의미는 아니다. 팀의 기존 워크플로우, Windows 환경 지원, 레거시 버전 필요 등 각자의 상황에 따라 적합한 도구는 다를 수 있다.

선택 기준

  • 안정성과 검증된 도구가 중요하다면 → NVM
  • 효율성과 자동화가 중요하다면 → Volta
  • Windows 네이티브가 필요하다면 → NVM
  • 여러 프로젝트를 자주 전환한다면 → Volta
  • 레거시 Node 버전이 필요하다면 → NVM
  • CI/CD 환경 최적화가 중요하다면 → Volta

둘 다 훌륭한 도구다. 자신의 상황에 맞는 선택을 하면 된다.


참고 자료