글 목록

Frontend

npm install, npm run dev, npm run build: 비슷해 보이지만 전혀 다른 세 명령어

npm install, npm run dev, npm run build는 각각 무엇을 할까요? dependency 설치부터 개발 서버, production build까지 세 명령어의 차이를 정리합니다.

JavaScript나 TypeScript 프로젝트를 처음 내려받으면 README에서 거의 항상 다음과 비슷한 명령어를 만나게 됩니다.

npm install
npm run dev
npm run build

처음에는 그냥 위에서부터 차례대로 실행하면 되는 명령어처럼 보입니다. 실제로 대부분 그렇게 하면 프로젝트가 실행됩니다.

하지만 세 명령어는 하는 일이 완전히 다릅니다.

  • npm install — 프로젝트에 필요한 dependencies를 설치합니다.
  • npm run dev — 프로젝트를 개발 환경(development environment) 에서 실행합니다.
  • npm run build — 프로젝트를 배포 가능한 production artifact로 빌드합니다.

이번 글에서는 각각의 명령어가 무엇을 하는지, package.json과는 어떤 관계가 있는지, 그리고 왜 npm run install이 아니라 npm install인지까지 정리해 보겠습니다.


1. npm은 무엇일까?

npm은 Node Package Manager입니다.

Node.js 프로젝트에서 외부 패키지를 설치하고, 버전을 관리하고, 프로젝트에서 정의한 script를 실행하는 역할을 합니다.

예를 들어 React 프로젝트에서 다음과 같은 코드를 작성했다고 해보겠습니다.

import React from "react";

프로젝트에서 React를 사용하려면 React 패키지가 필요합니다.

이러한 dependency 정보는 일반적으로 package.json에 기록됩니다.

{
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}

그렇다고 package.json 안에 React의 실제 코드가 들어 있는 것은 아닙니다.

package.json은 프로젝트가 어떤 dependency를 필요로 하는지 설명하는 파일에 가깝습니다.

실제 패키지를 로컬 환경으로 가져오는 과정이 필요합니다.

여기에서 등장하는 명령어가 npm install입니다.


2. npm install — 필요한 패키지를 준비한다

npm install

줄여서 다음과 같이 사용할 수도 있습니다.

npm i

npm install을 실행하면 npm은 프로젝트의 package.json과 lockfile을 기준으로 필요한 dependency를 설치합니다.

설치된 패키지는 일반적으로 다음 디렉터리에 저장됩니다.

my-project/
├── node_modules/
├── package.json
└── package-lock.json

node_modules

node_modules에는 프로젝트가 사용하는 실제 패키지 코드가 들어갑니다.

React 하나를 설치했는데도 node_modules 안에 수많은 디렉터리가 생기는 것을 볼 수 있습니다.

이유는 간단합니다.

우리가 사용하는 dependency도 또 다른 dependency를 가지고 있기 때문입니다.

My Application
      │
      ├── package-a
      │      ├── package-c
      │      └── package-d
      │
      └── package-b
             └── package-e

npm은 이러한 dependency tree를 해석하고 프로젝트 실행에 필요한 패키지를 설치합니다.

그래서 GitHub에서 프로젝트를 clone한 직후에는 보통 다음과 같은 순서가 됩니다.

git clone <repository>
cd <project>

npm install
npm run dev

대부분의 프로젝트에서 node_modules는 Git에 포함하지 않습니다.

node_modules/

Repository에는 package.json과 package-lock.json을 저장하고, 각 개발자가 프로젝트를 내려받은 뒤 dependency를 다시 설치하는 방식입니다.


3. package-lock.json은 왜 생길까?

npm install을 실행하면 자주 만나게 되는 또 하나의 파일이 있습니다.

package-lock.json

예를 들어 package.json에 다음 dependency가 있다고 해보겠습니다.

{
  "dependencies": {
    "some-package": "^2.3.0"
  }
}

^2.3.0은 정확히 2.3.0만 사용한다는 의미가 아닙니다. SemVer 규칙 안에서 호환되는 더 새로운 버전을 허용합니다.

그런데 개발자마다 서로 다른 dependency version이 설치된다면 이런 문제가 발생할 수 있습니다.

제 컴퓨터에서는 되는데요?

package-lock.json은 실제로 해석된 dependency tree와 버전을 기록해 설치 결과를 더 재현 가능하게 만드는 역할을 합니다.

특히 CI/CD처럼 dependency 설치 결과의 일관성이 중요한 환경에서는 lockfile을 함께 관리하는 것이 중요합니다.

이런 환경에서는 흔히 다음 명령어도 사용합니다.

npm ci

npm ci는 lockfile을 기반으로 clean install을 수행하기 때문에 CI 환경에서 특히 유용합니다.


4. npm run dev — 개발 서버를 실행한다

dependency를 설치했다면 이제 애플리케이션을 실행할 차례입니다.

많은 프로젝트에서는 다음 명령어를 사용합니다.

npm run dev

여기서 중요한 점이 하나 있습니다.

dev는 npm 자체에 내장된 명령어가 아닙니다.

package.json을 열어보면 보통 다음과 같은 부분을 발견할 수 있습니다.

{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

npm run dev의 의미는 사실상 다음과 같습니다.

package.json의 scripts에서 dev라는 이름의 script를 찾아 실행해 줘.

즉,

npm run dev

를 실행하면 npm이 다음 항목을 찾습니다.

"dev": "vite"

그리고 실제로는 Vite가 실행됩니다.

프로젝트에 따라 내용은 달라질 수 있습니다.

Next.js라면 다음과 같을 수 있습니다.

{
  "scripts": {
    "dev": "next dev"
  }
}

다른 프로젝트에서는 이렇게 되어 있을 수도 있습니다.

{
  "scripts": {
    "dev": "node server.js"
  }
}

그래서 npm run dev가 정확히 무엇을 하는지 알고 싶다면 가장 먼저 확인할 곳은 **package.json의 scripts**입니다.


5. 개발 서버는 왜 필요한가?

Frontend 프로젝트에서 npm run dev를 실행하면 보통 terminal에 다음과 비슷한 메시지가 나타납니다.

Local: http://localhost:5173/

브라우저에서 해당 주소를 열면 개발 중인 애플리케이션을 확인할 수 있습니다.

Development server는 단순히 파일을 보여주는 것 이상의 역할을 합니다.

Vite 같은 개발 도구를 사용한다면 파일을 수정했을 때 변경 사항을 빠르게 브라우저에 반영할 수 있습니다.

예를 들어,

<h1>Hello</h1>

를

<h1>Hello, npm!</h1>

으로 수정하면 매번 production build를 새로 만들 필요 없이 변경된 결과를 바로 확인할 수 있습니다.

이러한 빠른 feedback loop가 dev 환경의 중요한 목적입니다.


6. npm run build — 배포할 결과물을 만든다

개발이 끝났다면 다음 명령어를 만나게 됩니다.

npm run build

이 명령어 역시 npm 자체에 build라는 고정된 동작이 있는 것은 아닙니다.

package.json의 script를 실행합니다.

예를 들어 Vite 프로젝트라면 다음과 같습니다.

{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

따라서,

npm run build

는 실질적으로 다음 명령을 실행합니다.

vite build

빌드가 완료되면 Vite 프로젝트에서는 일반적으로 dist 디렉터리가 생성됩니다.

my-project/
├── dist/
│   ├── assets/
│   └── index.html
├── src/
├── package.json
└── package-lock.json

다른 framework나 build tool을 사용하면 .next, build 등 다른 형태의 output이 만들어질 수도 있습니다.

즉, build 결과의 위치와 형태는 npm이 아니라 실제 build tool과 framework가 결정합니다.


7. dev와 build는 무엇이 다를까?

둘의 가장 큰 차이는 목적입니다.

Development

npm run dev

개발자가 코드를 작성하고 빠르게 결과를 확인하기 위한 환경입니다.

일반적으로 다음과 같은 기능을 기대할 수 있습니다.

  • development server 실행
  • 빠른 rebuild
  • Hot Module Replacement(HMR)
  • debugging에 유리한 환경
  • source 변경 사항의 빠른 반영

Production Build

npm run build

실제 배포에 사용할 결과물을 만드는 과정입니다.

사용하는 framework와 bundler에 따라 다르지만 일반적으로 다음과 같은 작업이 수행될 수 있습니다.

  • bundling
  • minification
  • tree shaking
  • code splitting
  • asset processing
  • production optimization

그래서 npm run dev로 잘 실행된다고 해서 production build까지 반드시 성공한다는 보장은 없습니다.

배포 전에는 별도로 다음을 확인하는 것이 좋습니다.

npm run build

8. 그런데 왜 npm run install이 아닐까?

여기에서 재미있는 차이가 하나 있습니다.

npm install
npm run dev
npm run build

왜 install에만 run이 없을까요?

install은 npm이 직접 제공하는 command이기 때문입니다.

npm install

반면 dev, build는 일반적으로 프로젝트 개발자가 package.json의 scripts에 정의한 이름입니다.

{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

따라서 npm에게 script를 실행하라고 알려주기 위해 run을 사용합니다.

npm run dev
npm run build

이 차이를 이해하면 세 명령어가 훨씬 자연스럽게 보입니다.


9. package.json을 보면 프로젝트 실행 방법이 보인다

처음 보는 Node.js 프로젝트를 받았다면 무작정 명령어를 실행하기 전에 package.json을 한번 확인해 보는 습관이 꽤 유용합니다.

예를 들어 다음과 같은 파일이 있다고 해보겠습니다.

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  }
}

여기서 프로젝트가 제공하는 명령어를 어느 정도 파악할 수 있습니다.

npm run dev
npm run build
npm run lint
npm run preview

등록된 script 전체를 확인하고 싶다면 다음 명령어도 사용할 수 있습니다.

npm run

특히 다른 사람이 만든 repository를 처음 분석할 때 유용합니다.


10. 전체 흐름으로 이해하기

새로운 프로젝트를 clone했다고 가정해 보겠습니다.

git clone <repository-url>
cd <repository>

이 시점에는 package.json과 package-lock.json은 있지만 node_modules가 없는 경우가 많습니다.

먼저 dependency를 준비합니다.

npm install

구조는 대략 다음과 같이 바뀝니다.

package.json
package-lock.json
      │
      ▼
  npm install
      │
      ▼
 node_modules/

이제 개발을 시작합니다.

npm run dev
source code
    │
    ▼
development server
    │
    ▼
localhost

개발이 끝나면 production build를 확인합니다.

npm run build
source code
    │
    ▼
 build tool
    │
    ▼
production output

결국 세 명령어를 하나의 흐름으로 표현하면 다음과 같습니다.

Install → Develop → Build

npm install
     ↓
npm run dev
     ↓
npm run build

11. 자주 만나는 오류

command not found

예를 들어 다음과 같은 오류가 발생할 수 있습니다.

vite: command not found

dependency가 아직 설치되지 않은 상태라면 먼저 다음을 실행해 볼 수 있습니다.

npm install

Missing script: "dev"

npm error Missing script: "dev"

이 경우에는 package.json에 dev script가 정의되어 있는지 확인해야 합니다.

{
  "scripts": {
    "start": "node server.js"
  }
}

이 프로젝트에는 dev가 없기 때문에 npm run dev를 실행할 수 없습니다.

README를 확인하거나,

npm run

으로 사용할 수 있는 scripts를 확인하는 편이 좋습니다.

dev는 되는데 build가 실패한다

개발 서버가 정상적으로 실행되어도 production build에서는 TypeScript 검사, module resolution, 환경 변수 설정 등에서 문제가 발견될 수 있습니다.

그래서 배포 직전에만 build를 실행하기보다 개발 과정에서도 주기적으로 다음 명령어를 확인해 두면 좋습니다.

npm run build

마무리

npm install, npm run dev, npm run build는 Node.js 기반 프로젝트를 접하면 너무 자주 사용하다 보니 어느 순간 의미를 생각하지 않고 입력하게 되는 명령어입니다.

하지만 역할을 구분하면 어렵지 않습니다.

npm install
→ 프로젝트에 필요한 dependency를 설치한다.

npm run dev
→ package.json의 dev script를 실행한다.

npm run build
→ package.json의 build script를 실행한다.

그리고 여기서 가장 중요한 포인트는 dev와 build의 실제 동작은 npm이 결정하지 않는다는 것입니다.

그 답은 프로젝트의 package.json에 있습니다.

처음 보는 JavaScript 프로젝트를 만났다면 README와 함께 이 부분부터 확인해 보는 것을 추천합니다.

{
  "scripts": {
    "dev": "...",
    "build": "..."
  }
}

이 작은 습관만으로도 프로젝트가 어떻게 실행되고 빌드되는지 훨씬 빠르게 파악할 수 있습니다.

Tags

태그

npmJavascriptpackage.jsonVite