Back to JavaScript
JavaScript
easy
junior

What are devDependencies in package.json, and how do they differ from regular dependencies?

`dependencies` are needed at runtime; `devDependencies` are only needed for building, testing, or linting. Consumers of your package install dependencies but skip devDependencies.

4 min read·~6 min to think through

package.json splits packages into buckets that affect what gets installed where:

  • dependencies — needed when the code actually runs (React, lodash, your DB driver).
  • devDependencies — only needed at dev time (TypeScript, ESLint, Jest, Vite).
  • peerDependencies — packages the consumer must provide (e.g., a React component library declares react as a peer so it shares the host's React).
  • optionalDependencies — installs are best-effort; failures don't break install.

Why the split matters:

  1. Library consumers. When your package is installed as a dependency by someone else, npm installs your dependencies but not your devDependencies. Putting Jest in dependencies would force every consumer to download Jest.
  2. Production installs. npm ci --omit=dev (or NODE_ENV=production npm install historically) skips dev deps, shrinking Docker images significantly.
  3. Dependency hygiene. Treating dev tooling as separate makes audits easier — a CVE in your bundler doesn't ship to prod runtimes.

For an application (not a library), the split matters less for the consumer (there is none), but it still affects production install size and CI.

Code

ts
{
  "dependencies": {
    "next": "^14",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "typescript": "^5",
    "eslint": "^8",
    "@types/react": "^18",
    "vitest": "^1"
  }
}
Typical split for a Next.js app
ts
# in CI / Dockerfile
npm ci --omit=dev
Skip dev deps in a production install

Follow-up questions

  • Why are @types/* packages typically devDependencies?
  • What's a peerDependency, and how does it differ from devDependencies?
  • What does `npm install --save-dev X` do?

Common mistakes

  • Putting test tooling in `dependencies` and bloating consumers' installs.
  • Putting actual runtime libs in `devDependencies` and breaking production at startup.
  • Forgetting `peerDependencies` for a component library, leading to duplicate React copies.

Performance considerations

  • Production-only installs cut Docker layer size and image cold-start times — significant in serverless.

Edge cases

  • Build-only deps (e.g. `tailwindcss`) belong in `devDependencies` for libraries but `dependencies` if a Next.js app needs them at *runtime* build (depends on platform).
  • Some hosting (e.g. Vercel) installs all deps regardless and runs build there — dev/runtime split still affects the runtime layer.

Real-world examples

  • Most React component libraries declare React as a peerDependency and put it in devDependencies for local development.

Senior engineer discussion

Senior signal: discuss peer-dep ranges, dependency hoisting in monorepos (pnpm strict vs npm flat), and how lockfile + omit=dev shape image security posture.

Related questions