본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Frontend

npm install이 하는 일: 내 컴퓨터와 앱에 생기는 변화

작성 2026. 10. 30.추가
목차12개 항목
출처와 참고

npm 공식 문서 「npm install」, 「folders」, 「scripts」와 Node.js 「Loading from node_modules folders」를 바탕으로 정리한 노트다.

패키지를 설치한다는 것

npm 패키지는 다른 사람이 써서 레지스트리(registry.npmjs.org)에 올린 코드 묶음이다. 설치는 그 코드를 내려받아 내 프로젝트 폴더에 두고, 내 코드가 import로 불러 쓸 수 있게 하는 일이다. 앱 스토어의 앱 설치와 달리 운영체제에 프로그램을 등록하지 않는다. 대부분은 프로젝트 폴더 안의 파일이 늘어나는 것으로 끝난다.

다만 설치는 남이 쓴 코드를 내 컴퓨터에 들여와, 경우에 따라 바로 실행하는 일이기도 하다. 그래서 "파일 몇 개가 생긴다"와 "모르는 코드가 내 권한으로 돈다"를 함께 알아야 한다.

npm install <패키지>가 하는 일

flowchart TB
  cmd["npm install axios"] --> s1["1. 해석: package.json·lock 파일을 읽고, 레지스트리에서 axios와<br/>그 의존성(의존성의 의존성까지)의 버전을 정한다"]
  s1 --> s2["2. 내려받기: 각 패키지의 tarball(.tgz)을 받아 ~/.npm/_cacache 에 캐시한다<br/>이미 캐시에 있으면 네트워크를 쓰지 않는다"]
  s2 --> s3["3. 검증: tarball의 해시를 lock 파일·레지스트리의 integrity 값과 맞춘다"]
  s3 --> s4["4. 배치: ./node_modules/ 에 풀어 놓는다<br/>겹치는 의존성은 위로 끌어올려(hoisting) 공유한다"]
  s4 --> s5["5. 명령 연결: 패키지가 bin을 선언했으면 node_modules/.bin/ 에 링크를 만든다"]
  s5 --> s6["6. 설치 스크립트 실행: preinstall·install·postinstall 이 있으면 실행한다"]
  s6 --> s7["7. 기록: package.json 의 dependencies 에 #quot;axios#quot;: #quot;^x.y.z#quot; 를 넣고<br/>package-lock.json 에 실제 설치된 정확한 버전·주소·해시를 쓴다"]
  s7 --> s8["8. 감사: 알려진 취약점을 레지스트리에 물어 요약(npm audit)을 출력한다"]
  • 의존성은 연쇄된다. 패키지 하나를 넣어도 그 패키지가 기대는 패키지까지 수십 개가 함께 들어온다. 무엇이 왜 들어왔는지는 npm ls <이름>, npm explain <이름>으로 본다.
  • 인자 없이 npm install을 치면 1~8을 package.json과 lock 파일 전체에 대해 수행한다. 저장소를 처음 받았을 때 하는 일이 이것이다. CI는 lock 파일을 그대로 따르는 npm ci를 쓴다.
  • dependencies·devDependencies·peerDependencies의 차이, lock 파일의 동작은 패키지 매니저에 있다.

내 컴퓨터에 생기는 변화

로컬 설치(기본)

위치생기는 것성격
./node_modules/패키지 코드 전체프로젝트 전용. 지워도 npm install로 다시 만든다. 커밋하지 않는다
./node_modules/.bin/패키지가 제공하는 명령(vite, eslint)전역 PATH에는 없다. npm run·npx가 이 폴더를 PATH에 붙여 실행한다(npm 스크립트)
./package.json, ./package-lock.json의존성 목록과 정확한 버전 기록커밋해서 팀이 같은 설치를 재현한다
~/.npm/_cacache/내려받은 tarball 캐시모든 프로젝트가 공유한다. 다음 설치가 빨라진다. npm cache clean --force로 비운다
~/.npm/_npx/npx로 임시 실행한 패키지프로젝트에 남지 않는다

로컬 설치는 그 프로젝트 폴더 밖의 동작을 바꾸지 않는다. 다른 프로젝트, 시스템 설정, 다른 앱은 그대로다. 디스크는 꽤 쓴다. 작은 앱도 node_modules가 수백 MB가 되는 일이 흔하고, 프로젝트마다 따로 생긴다(pnpm은 전역 저장소에 한 벌만 두고 링크해 이 중복을 줄인다).

전역 설치(-g)

npm install -g <패키지>는 프로젝트가 아니라 Node.js 설치 위치(prefix)에 넣고, 명령을 셸 PATH에 올려 어느 폴더에서나 쓸 수 있게 한다.

Node 설치 방식전역 패키지 위치 (npm config get prefix로 확인)
nvm·fnm 같은 버전 관리자~/.nvm/versions/node/<버전>/lib/node_modules (홈 폴더 안. sudo 불필요, Node 버전마다 따로)
공식 설치 프로그램·Homebrew(macOS)/usr/local/lib/node_modules 또는 /opt/homebrew/lib/node_modules
Windows%AppData%\npm\node_modules
  • 전역 설치는 이 컴퓨터의 모든 셸에서 그 명령 이름을 차지한다. 프로젝트마다 다른 버전이 필요한 도구(린터, 번들러)는 전역 대신 devDependencies + npx로 쓴다. 전역은 프로젝트와 무관한 CLI(예: vercel, corepack)에만 쓴다.
  • sudo npm install -g가 필요하다면 Node가 시스템 폴더에 깔려 있다는 뜻이다. 설치 스크립트까지 root 권한으로 돌게 되므로 버전 관리자로 Node를 다시 까는 편이 안전하다.

설치 스크립트: 실제로 코드가 실행된다

패키지의 package.json에 preinstall·install·postinstall이 있으면 npm은 설치 중에 그 명령을 내 사용자 권한으로 실행한다. 원래 용도는 네이티브 모듈 컴파일(node-gyp), 플랫폼별 바이너리 내려받기 같은 준비 작업이다. 하지만 같은 권한으로 홈 폴더의 파일, SSH 키, .env, 토큰을 읽고 네트워크로 보낼 수도 있다. 공급망 공격이 자주 쓰는 통로다.

대응방법
스크립트를 끄고 설치npm install --ignore-scripts, 또는 .npmrc에 ignore-scripts=true
기본으로 막는 매니저pnpm 10+, Yarn 4.14+는 의존성 스크립트를 기본 차단하고 허용 목록만 실행한다
들이기 전에 확인이름 오타(typosquatting), 주간 다운로드, 관리 상태, 최근 소유자 변경을 본다

npm 자체는 여전히 기본으로 스크립트를 실행한다. 매니저별 기본값은 패키지 매니저의 「공급망 보안 기본값」, 깊은 의존성 트리가 왜 위험한지는 작은 모듈 철학과 npm 의존성에 있다.

앱에 생기는 변화

코드가 패키지를 찾는 방식

import axios from 'axios'처럼 경로 없는 이름을 쓰면 Node.js(와 번들러)는 현재 파일의 폴더부터 위로 올라가며 node_modules/axios를 찾는다. 찾으면 그 패키지 package.json의 exports(없으면 main)가 가리키는 파일을 불러온다. 그래서 설치만 하고 import하지 않은 패키지는 앱 동작에 아무 영향이 없고, package.json에 적지 않았는데 우연히 node_modules에 있는 패키지도 불러와진다(유령 의존성, pnpm은 이를 막는다).

결과물에 들어가는 것

앱 종류배포 결과물에 실리는 것
번들러를 쓰는 프런트엔드(Vite, Next.js 클라이언트)node_modules 전체가 아니라 실제로 import한 코드만 번들에 합쳐진다. 트리 셰이킹되는 패키지면 쓰는 함수만 들어간다. 사용자가 내려받는 JS 크기가 이만큼 늘어난다
Node.js 서버서버에서 npm ci --omit=dev로 dependencies를 설치해 그대로 쓴다. 이미지·함수 크기와 콜드 스타트에 영향을 준다
라이브러리(내가 배포하는 패키지)내 dependencies가 내 패키지를 쓰는 사람의 node_modules에도 설치된다

devDependencies(번들러, 테스트 도구, 타입 정의)는 개발 컴퓨터와 CI에서만 쓰이고 사용자에게 가지 않는다. 번들 크기를 보려면 빌드 결과 리포트나 vite-bundle-visualizer 같은 분석 도구를 쓴다.

함께 떠안는 것

  • 유지보수: 의존성의 버그·취약점·주요 버전 변경이 내 앱의 일이 된다. npm outdated, npm audit, Dependabot·Renovate로 관리한다.
  • 버전 범위: ^1.2.0은 1.x 안에서 새 버전을 받아들인다는 뜻이다. lock 파일이 있어야 모두가 같은 버전을 쓴다(유의적 버전).
  • 라이선스: 패키지의 라이선스(MIT, Apache-2.0, GPL)가 내 배포 조건에 붙는다. npx license-checker로 확인한다.

되돌리기

하고 싶은 것명령
패키지 제거npm uninstall <이름>: node_modules, package.json, lock 파일에서 함께 지운다
전역 패키지 제거npm uninstall -g <이름>, 목록은 npm ls -g --depth=0
설치 상태를 lock 파일 그대로 다시 만들기rm -rf node_modules && npm ci
캐시 비우기npm cache clean --force

node_modules를 지우는 것은 언제나 안전하다. 반대로 package-lock.json을 지우면 다음 설치에서 범위 안의 새 버전이 들어올 수 있으니 이유가 있을 때만 지운다. 설치 스크립트가 홈 폴더나 시스템에 따로 써 둔 파일은 uninstall로 지워지지 않는다.

정리

  • 설치는 레지스트리의 코드를 받아 캐시(~/.npm)와 프로젝트의 node_modules에 두고, package.json·lock 파일에 기록하는 일이다.
  • 로컬 설치는 그 프로젝트 폴더만 바꾼다. 전역 설치(-g)는 Node 설치 위치에 넣고 명령을 PATH에 올린다.
  • 설치 스크립트는 내 권한으로 실행된다. 모르는 패키지는 --ignore-scripts로 받거나 기본 차단 매니저를 쓴다.
  • 앱에는 import한 코드만 영향을 준다. 프런트엔드는 번들 크기로, 서버는 배포 크기로 드러나고, 유지보수·라이선스도 함께 들어온다.
  • 패키지 하나를 넣는 것은 남의 코드와 그 의존성 전체를 신뢰하겠다는 결정이다.

관련 노트: 자바스크립트 패키지 매니저, npm 스크립트로 작업 자동화하기, 작은 모듈 철학과 npm 의존성, 유의적 버전, 번들러의 역사와 Vite

원본 기록4

댓글

GitHub 계정으로 댓글을 남길 수 있어요.