Steven's Knowledge
Troubleshooting

Dependencies

Dependency management problems, version conflicts, and package management strategies

Dependency Troubleshooting

Dependency management issues can silently introduce bugs, security vulnerabilities, and build failures. This guide covers common problems and systematic solutions.


1. Version Conflicts — "Works on My Machine"

Problem

npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! peer dep missing: react@^17.0.0, required by some-library@2.3.4
npm ERR! Found: react@18.2.0
  • Different team members get different node_modules
  • Library X requires React 17, but project uses React 18
  • Transitive dependency conflicts

Root Cause

  • Lock file not committed or inconsistent
  • Using npm install instead of npm ci in CI
  • Incompatible peer dependency requirements

Solution

Always commit lock files:

# Ensure lock file is committed
git add package-lock.json  # npm
git add pnpm-lock.yaml     # pnpm
git add yarn.lock           # yarn

Use npm ci in CI/CD (not npm install):

# CI pipeline
steps:
  - run: npm ci  # Installs exactly from lock file — deterministic

Resolve peer dependency conflicts:

// package.json — npm overrides
{
  "overrides": {
    "some-library": {
      "react": "^18.0.0"
    }
  }
}
// package.json — pnpm overrides
{
  "pnpm": {
    "overrides": {
      "react": "^18.0.0"
    }
  }
}

2. Phantom Dependencies — Accessing Undeclared Packages

Problem

// This works locally because `lodash` is a transitive dependency
// But breaks when the parent dependency stops using lodash
import _ from 'lodash'; // Not in package.json!

Root Cause

npm and Yarn Classic use flat node_modules — you can import any package installed anywhere in the tree, even if you didn't declare it.

Solution

Use pnpm (strict by default):

# pnpm creates a strict node_modules structure
# Only declared dependencies are accessible
npm install -g pnpm
pnpm install

Or enable strict mode in npm/Yarn:

// .npmrc
strict-peer-dependencies=true

// Yarn Berry (.yarnrc.yml)
nodeLinker: pnpm  # Strict node_modules layout

Audit for phantom dependencies:

# depcheck finds undeclared and unused dependencies
npx depcheck

# Output:
# Missing dependencies:
#   lodash: used in src/utils.ts but not in package.json
# Unused dependencies:
#   moment: declared but never imported

3. Security Vulnerabilities in Dependencies

Problem

npm audit
# found 15 vulnerabilities (3 moderate, 8 high, 4 critical)

Solution

Automated vulnerability scanning:

# npm built-in audit
npm audit
npm audit fix         # Auto-fix compatible updates
npm audit fix --force # Breaking changes — review carefully

# Better: use dedicated tools
npx better-npm-audit audit

GitHub Dependabot (automated PRs for updates):

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    groups:
      production:
        patterns:
          - "*"
        exclude-patterns:
          - "@types/*"
          - "eslint*"
      dev:
        patterns:
          - "@types/*"
          - "eslint*"
    open-pull-requests-limit: 10

Pin critical dependencies:

{
  "dependencies": {
    "react": "18.2.0",          // Exact version for framework
    "next": "14.1.0"            // Exact version for framework
  },
  "devDependencies": {
    "@types/react": "^18.2.0"   // Range OK for type definitions
  }
}

4. Bundle Size Bloat from Dependencies

Problem

A single dependency import pulls in an unexpectedly large amount of code:

import { format } from 'date-fns'; // Pulls in 200KB+ if not tree-shaken
import moment from 'moment';        // 330KB minified

Solution

Check package size before installing:

# Check bundle impact
npx bundlephobia moment
# moment@2.29.4: 330kB min, 72.5kB min+gzip

npx bundlephobia date-fns
# date-fns@3.3.1: 78.2kB min (tree-shakeable to ~5kB for single function)

Import analysis in CI:

// package.json
{
  "scripts": {
    "size": "size-limit",
    "size:check": "size-limit --ci"
  },
  "size-limit": [
    { "path": "dist/index.js", "limit": "50 kB" },
    { "path": "dist/vendor.js", "limit": "150 kB" }
  ]
}

Use Import Cost VS Code extension to see package size inline.


5. Dependency Update Breaking Changes

Problem

npm update
# Everything breaks because a minor version had breaking changes
# Or: updating one package cascades updates to 50 others

Solution

Update strategy — staged approach:

# 1. Check what's outdated
npm outdated

# 2. Update patch versions only (safest)
npm update --save

# 3. Check for major updates separately
npx npm-check-updates

# 4. Update major versions one at a time
npx npm-check-updates -u --target minor  # Minor + patch only
npx npm-check-updates -u -f react        # Single package

Automated testing after updates:

# GitHub Actions — test after Dependabot PR
on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run typecheck
      - run: npm test
      - run: npm run build
      - run: npx playwright test  # E2E tests catch UI regressions

Renovate for advanced update management:

// renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "automerge": true
    },
    {
      "matchUpdateTypes": ["major"],
      "labels": ["breaking-change"],
      "automerge": false
    },
    {
      "groupName": "testing",
      "matchPackagePatterns": ["jest", "vitest", "@testing-library"]
    }
  ]
}

6. Monorepo Dependency Issues

Problem

  • Duplicate dependencies across packages
  • Version mismatches between workspace packages
  • Slow install times in CI
  • Hoisting conflicts

Solution

Workspace dependency management:

// pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'
// Root package.json — shared dependencies
{
  "pnpm": {
    "overrides": {
      "react": "^18.2.0",
      "react-dom": "^18.2.0",
      "typescript": "^5.3.0"
    }
  }
}

Workspace protocol for internal packages:

// apps/web/package.json
{
  "dependencies": {
    "@myorg/ui": "workspace:*",     // Always uses local version
    "@myorg/utils": "workspace:^1.0.0"  // Semver within workspace
  }
}

Turborepo for cached builds:

// turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "lint": {}
  }
}

7. Lock File Merge Conflicts

Problem

Every PR touches package-lock.json or yarn.lock, causing painful merge conflicts.

Solution

For npm — auto-resolve with npm install:

# After merge conflict in package-lock.json
git checkout --theirs package-lock.json
npm install  # Regenerates lock file from merged package.json
git add package-lock.json

Git merge driver for lock files:

# .gitattributes
package-lock.json merge=npm-merge-driver
pnpm-lock.yaml merge=pnpm-merge-driver
# Install the merge driver
npx npm-merge-driver install

Reduce conflicts — batch dependency updates:

// renovate.json — group updates to reduce PRs
{
  "packageRules": [
    {
      "groupName": "all non-major",
      "matchUpdateTypes": ["minor", "patch"],
      "schedule": ["before 8am on Monday"]
    }
  ]
}

Summary: Dependency Management Best Practices

PracticeTool/Approach
Lock file consistencyCommit lock files, use npm ci in CI
Strict dependenciespnpm (default), or npm with .npmrc
Security scanningnpm audit, Dependabot, Snyk
Bundle size controlbundlephobia, size-limit, Import Cost
Update strategyStaged updates, automated testing, Renovate
Monorepo managementpnpm workspaces, Turborepo caching
Merge conflictsnpm-merge-driver, batched updates
Unused deps cleanupdepcheck, knip

On this page