Basics

dependencies vs devDependencies — the difference, and why it breaks your deploy

Illustration: two crates by a workshop door — one holds a finished chair, the other keeps the saw and the plane

The surprise first. For an ordinary Vite or React site, it barely matters which section of package.json a package ends up in. The bundler puts everything you imported into the final code — from either section.

Now the other half. That same "doesn't matter" difference crashes your deploy with sh: vite: not found. It builds on your laptop and fails on the server. Let's see when the section decides everything and when it decides nothing.

What the two lists are

In package.json, dependencies live in two lists:

  • dependencies — what the program needs to run. React, a database client, a date library.
  • devDependencies — what the developer needs to build and check it. The Vite bundler, TypeScript, a linter, tests, @types/....

An analogy: you're making a chair to order. Boards and screws are dependencies — they become part of the chair. The saw and plane are devDependencies. You can't make the chair without them, but you don't ship them to the customer.

They're added with different commands:

npm install dayjs           # into dependencies
npm install -D vitest       # into devDependencies (-D = --save-dev)
pnpm add -D vitest          # the same in pnpm

Point-by-point comparison

  • Installed by npm install on your machine: dependencies — yes; devDependencies — yes. Locally there's no difference at all.
  • Installed by npm install when NODE_ENV=production: dependencies — yes; devDependencies — no. npm skips them by default.
  • Installed by npm ci --omit=dev: dependencies — yes; devDependencies — no. That's how slim Docker images are built.
  • Ends up in the site bundle: the import decides, not the section. Import it in code and the bundler includes it, wherever it's listed.
  • Installed for people who install your library from npm: dependencies — yes; devDependencies — never.
  • Typical contents: dependencies — React, Express, the Supabase client; devDependencies — Vite, TypeScript, ESLint, Vitest, Prettier.

Where the difference hurts

1. Building on a server. This is that vite: not found error. A common scenario is a Dockerfile:

ENV NODE_ENV=production
RUN npm ci                 # devDependencies skipped
RUN npm run build          # ✗ vite: not found

NODE_ENV=production sits above the install, so npm didn't install Vite. And you can't build without it. The fix is to install everything for the build and switch on production afterwards:

RUN npm ci
RUN npm run build
ENV NODE_ENV=production

Other common deploy failures are collected in why my deploy fails.

2. A Node server. If your Express backend runs without a build step, production installs only dependencies. Put express in devDependencies and the server won't start: Cannot find module 'express'. Locally it all works, because at home everything gets installed.

3. Your own library. If you publish a package to npm, whoever installs it gets only your dependencies. Forget a needed package there and it works for you but breaks for every user.

And what's the third section — peerDependencies

Sometimes another library's package.json has one more list — peerDependencies. It's a request: "I'm a React plugin, but I won't bring my own React — use the one already in the project". Otherwise the project would end up with two copies of React, and hooks would break. You'll almost never need this section in your own site. But if npm complains about a peer dependency during install, that's what it means: your React version isn't the one the plugin expects.

Which one to pick

No fence-sitting:

  • A site or frontend on Vite / Next: put everything that's only for building and checking into devDependencies, the rest into dependencies. Get a detail wrong and the site won't break. Just don't set NODE_ENV=production before installing.
  • A Node server without a build: be strict. Anything that's required or imported at runtime goes in dependencies only.
  • A library for npm: strictest of all. Test the install in a clean folder before publishing.

How do I move a package from one section to the other?

Install it again with the right flag: npm install -D eslint moves it into devDependencies, and npm install eslint moves it back. Or edit package.json by hand and run npm install.

What does the lockfile do with devDependencies?

package-lock.json records every package from both sections. The --omit=dev flag doesn't change the file — it just skips putting the extras on disk. For the different package managers, see npm vs pnpm vs yarn.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app
KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →