npm run dev 와 npm start 는 뭐가 다른가 — 예약어와 관례의 두 층위
npm run dev 로 띄우던 앱을 서버에선 npm start 로 띄우라고 한다. 같은 앱인데 왜 명령이 둘인가, 아무거나 쓰면 안 되는가 — 답은 한 문장으로 줄일 수 있다. start 는 npm 의 예약어고, dev 는 팀의 관례일 뿐이다. 다만 그 관례 위에 프레임워크들이 “개발 모드 vs 프로덕션 모드” 라는 실질적 차이를 얹어 놨기 때문에, 아무거나 쓰면 정말로 안 된다. 층위를 나눠서 보자.
1층 — npm 이 아는 것: start 는 예약어, dev 는 그냥 이름
npm 자체의 규칙은 의외로 단순하다.
npm start 는 라이프사이클 명령이다. package.json 의 scripts.start 를 실행하며, test·stop·restart 와 함께 run 없이 바로 부를 수 있는 소수의 예약 명령에 속한다1. 두 가지 특수 동작이 따라온다:
- 기본값이 있다.
start스크립트를 정의하지 않아도npm start는 실패하지 않고node server.js를 실행하려 든다2. 루트에server.js가 없으면 그제서야 에러다 — “스크립트가 없다” 가 아니라 “server.js 를 못 찾는다” 는 엉뚱한 메시지로. - 훅이 걸린다.
prestart/poststart를 정의해 두면 앞뒤로 자동 실행된다1.
npm run dev 는 임의 스크립트 실행이다. npm run 은 npm run-script 의 별칭으로, scripts 객체에 적힌 아무 명령이나 이름으로 실행해 주는 범용 러너다3. dev 라는 이름에 npm 이 부여한 의미는 없다. npm run banana 와 지위가 같다. 우리가 dev 를 특별하게 느끼는 건 순전히 생태계의 관례 때문이다.
즉 1층에서의 차이는 “예약어 + 기본값 + 훅” vs “그냥 이름” 이 전부다. 진짜 차이는 그 이름 아래에 뭘 적어 두느냐에서 나온다.
2층 — 프레임워크가 얹은 것: 개발 서버 vs 프로덕션 서빙
메이저 프레임워크들은 이 두 이름에 일관된 관례를 실어 놨다.
Next.js 가 가장 전형적이다. 생성되는 scripts 는 dev: next dev / build: next build / start: next start 다. 공식 CLI 문서 기준 next dev 는 개발 모드 — HMR(핫 리로드)·상세 에러 리포팅이 켜진 개발 서버를 띄우고, next start 는 프로덕션 모드 — next build 로 컴파일된 결과물을 서빙한다4. 빌드 없이 next start 를 치면 빌드부터 하라고 거절당한다. 같은 코드라도 두 모드는 컴파일 최적화·캐싱 동작이 다르다.
Vite 는 이름으로 오해를 차단한 쪽이다. vite(dev 서버)·vite build·그리고 start 대신 vite preview 를 둔다. preview 는 빌드 산출물을 로컬에서 확인하는 용도이고, 공식 문서가 명시적으로 경고한다 — “Do not use this as a production server as it’s not designed for it”5. Vite 세계에서 프로덕션 서빙은 빌드된 정적 파일을 nginx 같은 별도 서버에 올리는 일이지, npm 스크립트의 일이 아니라는 설계다.
Express 는 반대편 극단이다. 클라이언트 빌드라는 개념이 없으니 generator 가 만들어 주는 스크립트는 start 하나뿐이다6. dev 용 자동 재시작(nodemon 류)을 원하면 그때 dev 스크립트를 손으로 추가하는 게 관례다.
정리하면 관례의 실체는 이렇다: dev = 소스를 직접 물고 도는 개발 서버 (빠른 피드백이 목적, 서빙 성능은 목적 아님) / start = 빌드 산출물을 서빙하는 프로덕션 실행 (빌드가 선행 조건).
사고가 나는 네 지점
- 운영 서버에서
npm run dev. 가장 흔하고 가장 나쁘다. dev 서버는 파일 감시·변환을 상시 수행하며 프로덕션 최적화(압축·캐싱)가 꺼져 있고, 애초에 다중 사용자 서빙용으로 설계되지 않았다 — Vite 는 preview 조차 프로덕션 서버로 쓰지 말라고 못 박는다5. Dockerfile·systemd 유닛의 실행 명령은build후start가 기본이다. start없이npm start. 위에서 본 대로node server.js폴백이 발동해, 스크립트 누락이 “server.js 없음” 이라는 오해하기 좋은 에러로 둔갑한다2.- 인자 전달.
npm start 3000처럼 붙인 인자는 스크립트에 안 간다 —npm start -- --port 3000처럼--뒤에 붙여야 스크립트로 전달된다3. dev가 늘 dev 인 건 아니다. 관례일 뿐이라 리포마다serve·watch·start:dev(NestJS 계열) 등으로 흩어져 있다. 남의 리포에선 이름을 믿지 말고package.json의scripts를 열어 실제 명령을 읽는 게 정답이다.
정리
npm run dev |
npm start |
|
|---|---|---|
| npm 에서의 지위 | 임의 스크립트 (run-script) |
예약 라이프사이클 명령 |
| 스크립트 없을 때 | 에러 | node server.js 폴백 |
| pre/post 훅 | predev/postdev 가능 |
prestart/poststart |
| 관례상 역할 | 개발 서버 (HMR, 소스 직결) | 프로덕션 빌드 서빙 |
| 선행 조건 | 없음 | 대개 npm run build |
| 쓰는 곳 | 로컬 개발 | 배포 환경 (Docker/CI) |
한 줄 요약 — npm 은 start 만 알고 dev 는 모른다. 하지만 프레임워크 관례가 dev=개발 서버, start=프로덕션 서빙으로 굳혀 놨으니, 로컬에선 dev 를, 배포 명령엔 build + start 를, 남의 리포에선 이름 대신 scripts 본문을 믿어라.
근거의 한계
- 프레임워크 관례는 Next.js·Vite·Express 세 곳의 공식 문서로만 실측했다. 다른 프레임워크(SvelteKit, Nuxt 등)도 대체로 같은 관례를 따르지만 이 글에서 검증하지는 않았다.
- “dev 서버가 프로덕션 대비 얼마나 느린가” 류의 정량 비교는 중립 출처를 확인하지 못해 싣지 않았다 — 이 글의 근거는 성능 수치가 아니라 각 도구의 공식 설계 서술이다.
References
-
npm 공식 문서 — npm-run (
run-script별칭, 임의 명령 실행,--인자 전달) ↩ ↩2 -
Next.js 공식 문서 — next CLI (
next dev개발 모드 /next start프로덕션 모드, 빌드 선행) ↩ -
Vite 공식 문서 — Command Line Interface (
vite preview— “Do not use this as a production server as it’s not designed for it”) ↩ ↩2 -
Express 공식 문서 — Express application generator (
npm start로 실행하는 기본 구조) ↩