Dependency Management Best Practices
Every dependency you add is a liability. It's code you didn't write, maintained by people you don't know, with bugs and vulnerabilities you can't predict. Yet modern applications have hundreds of them. The average npm project pulls in over 1,000 transitive dependencies.
This isn't inherently bad - dependencies let us build faster. But they need management. Here's how to do it well.
The Lockfile Is Sacred
Your lockfile (package-lock.json, yarn.lock, pnpm-lock.yaml, Cargo.lock, etc.) is the only thing guaranteeing reproducible builds. Commit it. Always.
I've seen teams gitignore their lockfiles "to reduce merge conflicts." Then they wonder why builds work locally but fail in CI, or why one developer has a bug nobody else can reproduce. Different versions of the same dependency, installed at different times.
Merge conflicts in lockfiles are annoying. You know what's more annoying? Debugging phantom issues because everyone has slightly different packages.
Semantic Versioning Is a Lie (Sort Of)
Semver promises that patch versions (1.0.X) are bug fixes, minor versions (1.X.0) add features backward-compatibly, and major versions (X.0.0) break things. In practice:
- Bug fixes sometimes break things
- Minor versions sometimes break things
- Maintainers make mistakes
- Your usage might be an edge case the maintainer didn't consider
This doesn't mean semver is useless - it's a helpful signal. But don't trust it blindly. Test after updates. Have a CI pipeline that catches breaks before they hit production.
Update Strategy: Little and Often
Two bad extremes: updating nothing (security nightmare) and updating everything constantly (stability nightmare).
What works: regular, small updates. Weekly or biweekly, run your update tooling, review the changes, test, and merge. Small batches are easier to debug when something breaks.
For npm:
npm outdatedshows what's behindnpm updateupdates within semver rangesnpx npm-check-updatesshows major version updates available
Automation helps. Dependabot, Renovate, and similar tools create PRs for updates automatically. Configure them to batch updates by type - security patches merged quickly, major versions queued for careful review.
Security Scanning
Dependencies have vulnerabilities. The question is whether you'll find them before attackers do.
Run security scans regularly:
npm audit(built into npm)- Snyk
- GitHub Dependabot alerts
- Socket.dev (also detects supply chain attacks)
But don't just run them - act on the results. A weekly ritual: check for new security advisories, prioritize by severity and exploitability, update or patch as needed.
Not every vulnerability is exploitable in your context. A regex DoS vulnerability in a library you only use at build time isn't urgent. A SQL injection in your ORM is critical. Learn to triage.
Transitive Dependencies: The Hidden Risk
You might have 20 direct dependencies, but each of those has dependencies, which have dependencies... Soon you have 1,500 packages, most of which you've never heard of.
The left-pad incident taught us this. A single 11-line package, removed from npm, broke thousands of builds. More recently, malicious code injected into ua-parser-js affected millions of projects.
Mitigation strategies:
Review before adding: Before adding a new dependency, check: Who maintains it? How active is development? How many downloads? How many open issues? A package with 3 downloads/week and the last commit 4 years ago is probably not production-ready.
Consider the dependency tree: Check what transitive dependencies come along. npm explain package-name shows why something is installed. You might find you're pulling in 50 packages for a utility that could be a few lines of code.
Lockfile integrity: Tools like socket.dev and lockfile-lint can detect suspicious changes in your lockfile that might indicate supply chain attacks.
When to Write It Yourself
Not everything needs a package. The trade-off:
Use a package when:
- The problem is complex (crypto, parsing, etc.)
- Security is critical and you're not an expert
- The package is well-maintained with good test coverage
- Writing it yourself would take days/weeks
Write it yourself when:
- It's a few lines of simple code
- The package has more features than you need (bloat)
- The package is unmaintained or sketchy
- You need full control over the implementation
The is-odd package checks if a number is odd. It has dependencies. You could write this in one line: const isOdd = n => n % 2 !== 0. Some things don't need packages.
Monorepos and Dependency Duplication
In monorepos, different packages often depend on different versions of the same library. You might have React 18.0 in one package and React 18.2 in another. This leads to:
- Larger bundle sizes (multiple copies of similar code)
- Subtle bugs when versions conflict
- Confusion about which version has which features
Use tools like pnpm (which handles this better than npm) or npm's deduplication. Enforce version consistency across the monorepo with tools like syncpack.
The Update Runbook
A practical process for teams:
Weekly (automated):
- Dependabot/Renovate creates PRs for patch updates
- CI runs tests automatically
- Green PRs get auto-merged or reviewed quickly
Weekly (manual):
- Review security advisories
- Prioritize and address critical vulnerabilities
- Check for PRs that need manual review
Monthly:
- Review minor version updates
- Check for deprecation warnings
- Clean up unused dependencies
Quarterly:
- Evaluate major version updates
- Review dependency landscape (any unmaintained critical deps?)
- Audit the full dependency tree
Documentation
Keep notes on your dependencies:
- Why each major dependency was chosen
- Known quirks or gotchas
- Migration plans for deprecated packages
- Which dependencies are candidates for removal
When the person who added a dependency leaves, this knowledge shouldn't leave with them.
The Bottom Line
Dependencies are powerful but require active management. Treat them like employees: hire carefully, review regularly, and don't be afraid to let go of ones that aren't working out. The goal is a dependency list that's intentional, secure, and maintainable. Not just whatever accumulated over the years.