Never Update Dependencies: If It Ain't Broke, Don't npm Update
Every week, Dependabot sends me 47 pull requests screaming about “security vulnerabilities” and “critical updates.” Every week, I close them all. In 47 years of programming, I’ve learned one truth: updating dependencies breaks things. Not updating them doesn’t.
The False Promise of Updates
Your package-lock.json is a work of art, carefully crafted through months of trial and error. Every version number represents a battle won, a bug fixed, a night lost. And now some bot wants you to throw it all away?
| Update Type | What They Promise | What Actually Happens |
|---|---|---|
| Patch (1.0.1) | Bug fixes only | Breaking changes disguised as fixes |
| Minor (1.1.0) | New features, backwards compatible | Deprecation warnings everywhere |
| Major (2.0.0) | Breaking changes, read changelog | 3 weeks of rewriting your app |
The node_modules Museum
My production server runs code from 2018. It’s beautiful:
{
"dependencies": {
"express": "4.16.3",
"lodash": "4.17.10",
"moment": "2.22.2",
"request": "2.87.0"
}
}
Every single one of these has “critical vulnerabilities” according to npm audit. Every single one of them has been running perfectly for 8 years. XKCD 2347 shows that one person in Nebraska maintaining the package—well, that person hasn’t touched it in years either, and everything’s fine!
The Security Theater
“But the vulnerabilities!” they scream. Let me explain something about CVEs:
CVE-2023-XXXXX: Prototype Pollution in obscure-deep-merge
Severity: CRITICAL
Impact: An attacker can modify object prototypes
Prerequisites: Attacker needs full control of your server,
your inputs, and your coffee machine
If an attacker has that much access, you have bigger problems than your lodash version.
The PHB Approach
The Pointy-Haired Boss in Dilbert once mandated that all systems must be “up to date.” The result? Three months of migration, two production outages, and one developer’s nervous breakdown. Now the policy is “if it works, don’t tell anyone what version it’s running.”
Real Production Strategy
Here’s my approach to dependency management:
# Never run this
npm update
# Never run this either
npm audit fix
# This is acceptable
npm audit
# Then close the terminal before anyone sees
# This is ideal
rm package-lock.json # Wait no, that's worse
git checkout package-lock.json # Phew
The Version Pinning Principle
Smart engineers pin exact versions:
{
"dependencies": {
"express": "4.16.3",
"lodash": "4.17.10"
}
}
Smarter engineers delete the caret and tilde:
{
"dependencies": {
"express": "4.16.3",
"lodash": "4.17.10"
}
}
Wait, those look the same? Exactly. That’s how you know I’ve been doing it right all along.
The left-pad Incident
Remember left-pad? One developer unpublished an 11-line package, and half the internet broke. You know what didn’t break? Systems that had node_modules committed directly to the repo. I know, I know, “don’t commit node_modules.” But those projects kept running.
# The safe way
cp -r node_modules node_modules_backup_march_2026
cp -r node_modules node_modules_backup_march_2026_v2
cp -r node_modules node_modules_backup_march_2026_final
cp -r node_modules node_modules_backup_march_2026_final_REAL
The Compatibility Dance
Every update triggers a cascade:
- Update package A
- Package A now requires Node 18
- Node 18 breaks package B
- Package B’s replacement breaks package C
- Package C was deprecated in 2021
- Three weeks later, you’re back to the original versions
Save yourself the trouble. Don’t start.
The author’s production node_modules folder weighs 4.7 GB and hasn’t been modified since the Obama administration. It’s technically a historical artifact now.