dependencies vs devDependencies

A package.json categorizes dependencies into four buckets. Understanding the exact difference between them is critical for bundle size optimization.

Package Managers4 min readConcept 7 of 24

The Four Dependency Buckets

A package.json file categorizes dependencies into four distinct buckets. Understanding the exact difference between them is critical for bundle size optimization, CI/CD pipeline speed, and security.

**dependencies** are your core production requirements. If your app imports a library (like react or lodash) to run in production, it goes here. **devDependencies** are your local tooling requirements. Things like jest, eslint, and typescript are needed to build and test the app, but they do not belong on the final production server.

Strict Contracts and Optional Enhancements

**peerDependencies** act as a strict contract. They tell npm: 'My package needs the host application to install this specific dependency'. This prevents installing multiple conflicting copies of large libraries like React.

Finally, **optionalDependencies** are non-critical features (often OS specific native bindings); if they fail to compile, the install continues successfully anyway.

Production Installs and ERESOLVE

When deploying to production or building a Docker container, you should run npm install --production (or --omit=dev). This tells npm to skip downloading and extracting everything in devDependencies, significantly reducing build time and the final image size.

Since npm version 7, peerDependencies are installed automatically by default. However, if the host application has a conflicting version installed, npm will immediately halt the installation and throw an ERESOLVE error. You can override this strict behavior by passing the --legacy-peer-deps flag to force npm to ignore the conflict and install anyway.

Dependency Deployment Simulator

Toggle between development and production install modes. Observe which dependencies actually ship to the production server and how peer and optional dependencies behave.

package.json
{
  "name": "my-app",
  "dependencies": {
    "react": "^18.2.0",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "jest": "^29.5.0",
    "typescript": "^5.0.4"
  },
  "peerDependencies": {
    "react-dom": "^18.2.0"
  },
  "optionalDependencies": {
    "fsevents": "^2.3.2"
  }
}
node_modules/
react
dep
lodash
dep
react-dom
peer
jest
dev
typescript
dev
fsevents
optional

ERESOLVE unable to resolve dependency tree

Conflicting peer dependency: react-dom@"^17.0.0" from the root project disagrees with "^18.2.0".

Standard install pulls down everything required for local development and testing.

Remember this

  • dependencies are shipped to production; devDependencies are skipped during production installs.
  • Use --omit=dev or set NODE_ENV=production to skip dev dependencies in CI/CD.
  • npm v7+ automatically installs peerDependencies and throws ERESOLVE errors on version conflicts.
  • If an optionalDependency fails to install (e.g., native compilation error), npm ignores the failure and succeeds.

Done with this concept?

Mark it complete to track your progress. No login needed.