Every npm package you install belongs in one of two categories: packages your application needs to run in production, and packages you only need during development. npm formalises this distinction with two separate fields in package.json: dependencies and devDependencies. Getting this right matters beyond just cleanliness — it directly affects your production deployment bundle size, your security attack surface, and how clearly your codebase communicates its architecture to other developers.
The Core Distinction
| dependencies | devDependencies | |
|---|---|---|
| Needed at | Runtime — server must have these to run | Development time only — not needed to run the app |
| Install flag | npm install pkg (no flag) |
npm install -D pkg |
| package.json key | "dependencies" |
"devDependencies" |
| Installed in production? | Yes — always | No — skipped with npm install --production |
| Examples | express, mongoose, jsonwebtoken, axios | nodemon, eslint, jest, prettier, supertest |
npm install --omit=dev (equivalent to the older --production flag) by default. This skips all devDependencies and only installs what is listed under dependencies. If you accidentally place a runtime package under devDependencies, your app will crash in production with Cannot find module even though it works perfectly on your local machine.require() this package to serve requests?” If yes, it is a production dependency. If the package is only used by scripts you run before or after the server starts (building, testing, linting), it is a dev dependency.devDependency for the React client — it is only used during development and to build the production bundle. The built output in dist/ is plain HTML, CSS, and JavaScript with no reference to Vite at all. However, react and react-dom are listed under dependencies in the client’s package.json because they are bundled into the final output by Vite during build.Classifying Your MERN Packages
| Package | Category | Reason |
|---|---|---|
| express | dependency | Server requires it to handle HTTP requests at runtime |
| mongoose | dependency | Server requires it to connect to MongoDB at runtime |
| dotenv | dependency | Server calls require('dotenv').config() on startup |
| cors | dependency | Express middleware active at runtime |
| jsonwebtoken | dependency | Used to sign/verify tokens in auth routes at runtime |
| bcryptjs | dependency | Used to hash passwords in register/login routes at runtime |
| nodemailer | dependency | Used to send emails from Express routes at runtime |
| nodemon | devDependency | Watches files and restarts server — only needed in development |
| eslint | devDependency | Lints code during development — not needed in production |
| jest | devDependency | Test runner — tests do not run in production |
| supertest | devDependency | HTTP testing helper — used only in test scripts |
| prettier | devDependency | Code formatter — formats source files during development |
| vite (client) | devDependency | Dev server and build tool — not present at runtime |
| react (client) | dependency | Bundled into the final JS output by Vite |
| axios (client) | dependency | Bundled into the final JS output by Vite |
Installing With the Correct Flag
# ── Server: production dependencies ──────────────────────────────────────────
cd server
npm install express mongoose dotenv cors helmet bcryptjs jsonwebtoken nodemailer multer
# ── Server: dev dependencies ──────────────────────────────────────────────────
npm install -D nodemon eslint jest supertest
# ── Client: production dependencies (bundled by Vite) ────────────────────────
cd ../client
npm install react react-dom react-router-dom axios
# ── Client: dev dependencies (build tools, linting) ──────────────────────────
npm install -D vite @vitejs/plugin-react eslint eslint-plugin-react
Production Install — What Gets Skipped
# On your local machine — installs everything including devDependencies
npm install
# In production / CI — skips devDependencies
npm install --omit=dev # npm 7+ (modern)
npm install --production # older syntax (same effect)
# Verify what would be installed in production
npm list --omit=dev --depth=0
Example: server/ node_modules in production vs development
Development install — includes:
express, mongoose, dotenv, cors, bcryptjs, jsonwebtoken (production)
nodemon, eslint, jest, supertest (dev — 4 extra packages, ~40MB)
Production install (--omit=dev) — includes only:
express, mongoose, dotenv, cors, bcryptjs, jsonwebtoken
→ Smaller image, faster deploy, smaller security surface
Common Mistakes
Mistake 1 — Installing runtime packages as devDependencies
❌ Wrong — dotenv installed as a dev dependency:
npm install -D dotenv # dotenv goes into devDependencies
Local: Works — all deps installed
Production: CRASH — "Cannot find module 'dotenv'"
process.env variables are never loaded → MongoDB URI is undefined
✅ Correct — dotenv is needed at runtime (server startup) so it belongs in production dependencies:
npm install dotenv # no -D flag ✓
Mistake 2 — Installing build tools as production dependencies
❌ Wrong — nodemon in production dependencies bloats the deployment:
npm install nodemon # goes into dependencies — deployed to production unnecessarily
✅ Correct — nodemon is a dev tool that only watches files during development:
npm install -D nodemon # devDependencies — excluded from production deploy ✓
Mistake 3 — Using nodemon in production
❌ Wrong — package.json start script using nodemon:
"scripts": {
"start": "nodemon index.js" // nodemon is a devDependency — not available in production!
}
✅ Correct — use node for production, nodemon only for development:
"scripts": {
"start": "node index.js", // production — plain Node.js
"dev": "nodemon index.js" // development — auto-restart on change
}
Quick Reference
| Question | Answer |
|---|---|
| Does the running server require() this package? | dependency — no flag |
| Is this only used during development (test, lint, build)? | devDependency — use -D |
| How to skip devDeps in production? | npm install --omit=dev |
| Does Render/Heroku skip devDeps automatically? | Yes — they run --omit=dev by default |
| Should nodemon be in start or dev script? | dev script only |