npm run dev vs npm run build vs npm start: What Each One Does
npm run dev starts a development server with hot reload; npm run build makes an optimised production version; npm start runs that production version. What each does, where the commands come from in package.json, and why you should never run dev mode in production.
Almost every JavaScript project has three commands you'll see over and over: npm run dev, npm run build and npm start. They do very different jobs.
Where these commands come from
They're defined in the "scripts" section of your package.json. (What are npm and package.json?) A typical Next.js project:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint ."
}
}
npm run dev simply runs whatever is next to "dev". A Vite project might have "dev": "vite" and "build": "vite build". The names are conventions — check your own package.json to see what they actually do.
(npm start and npm test are special: you can leave out run.)
npm run dev — for working on the app
Starts a development server, usually at http://localhost:3000 or 5173. (What is localhost?)
- Hot reload — save a file and the browser updates instantly.
- Helpful errors — detailed messages and overlays.
- Unoptimised — code isn't minified; pages may compile on first visit.
- Slower and heavier than production, by design.
This is what you run while building.
npm run build — make the production version
Creates an optimised version of your app, ready to deploy:
- Compiles TypeScript and JSX into plain JavaScript
- Minifies and bundles code, splits it into chunks, adds hashed file names
- Pre-renders static pages (in frameworks like Next.js and Astro)
- Fails if there are type errors or broken imports
The output goes into a folder like dist/ (Vite) or .next/ (Next.js). It doesn't run anything — it just produces files.
npm start — run the production version
Runs the built app as a production server. For Next.js that's next start, which serves the .next/ build. For a Node/Express API it's often node server.js.
Pure front-end apps (plain React + Vite) usually don't have a production server at all: the dist/ folder is static files you upload to a host or serve with Nginx. vite preview lets you check the build locally. (Deploy a React app)
Side by side
npm run dev |
npm run build |
npm start |
|
|---|---|---|---|
| Purpose | Develop | Prepare for production | Run in production |
| Hot reload | Yes | — | No |
| Speed | Slow, unoptimised | — | Fast, optimised |
| Output | A running dev server | Files (dist/, .next/) |
A running production server |
| Use on a live server? | No | Yes, before start | Yes |
Don't run dev mode in production
It's surprisingly common — especially with AI-built apps — to see npm run dev running on a live server. Problems:
- Much slower and uses far more memory
- Shows detailed error messages (and sometimes source code) to visitors
- Not designed to stay up under real traffic
On a server, the sequence is npm ci → npm run build → npm start (kept running by a process manager). (PM2 vs systemd, Deploy a Node.js app to a VPS)
When build fails but dev works
This happens a lot: dev mode is forgiving, the production build is strict. Type errors, lint errors, case-sensitive imports and missing environment variables often only show up in build. (npm run build fails but dev works)
Run npm run build locally before deploying — it catches most deploy failures in seconds.
EasySpawn builds your app and runs the production version for you — the right command, kept running, with HTTPS in front — so dev mode never ends up on your live site. See how it works or join the waitlist.
Related: What Are npm and package.json? · npm run build Fails but Dev Works · Dev, Staging, and Production Explained · Webpack vs Vite
Keep reading
GitHub Actions Secrets: How to Store and Use Them Safely
GitHub Actions secrets keep API keys, tokens and passwords out of your code while letting workflows use them. How to add repository, environment and organisation secrets, use them in a workflow, secrets vs variables, the fork and pull_request_target pitfalls, and OIDC instead of long-lived keys.
"JavaScript Heap Out of Memory": Why It Happens and How to Fix It
FATAL ERROR: Reached heap limit — JavaScript heap out of memory. What Node's heap limit is, why builds and servers hit it, how to raise it safely with --max-old-space-size, when the real problem is a memory leak, and the difference from exit code 137.