# Debugging Hell: How Verdaccio Configuration Broke My Vercel Deployment

Table of Contents

Prologue

Last Friday at 5 PM, I confidently typed vercel --prod into my terminal, ready to show the client our new features. The Vercel build immediately exploded with a cryptic error:

Terminal window
Error: Command "npm install" exited with 1

That’s it? That’s all you’re giving me?

The Crime Scene: A Mysterious Build Failure

Here’s what the full error log looked like:

2025-10-04T08:55:11.987Z Running build in Washington, D.C., USA (East) – iad1
2025-10-04T08:55:17.848Z Installing dependencies...
2025-10-04T08:56:38.421Z npm error Exit handler never called!
2025-10-04T08:56:38.421Z npm error This is an error with npm itself. Please report this error at:
2025-10-04T08:56:38.421Z npm error <https://github.com/npm/cli/issues>
2025-10-04T08:56:38.451Z Error: Command "npm install" exited with 1

“An error with npm itself”? I haven’t done anything unusual… or have I?

First Instinct: Standard Troubleshooting

Terminal window
# 1. Clear Vercel build cache
$ vercel --prod --force
# Failed - Same error
# 2. Test locally
$ rm -rf node_modules
$ npm install
# Success - Works perfectly on my machine
# 3. Check Node version

Added to package.json:

{
"engines": {
"node": "18.x",
"npm": "9.x"
}
}
Terminal window
$ git add package.json
$ git commit -m "fix: specify node version"
$ git push
$ vercel --prod
# Failed - Still failing

It’s 6 PM now. The client is asking for updates on WeChat.

Down the Rabbit Hole: Trial and Error

Attempt 1: Suspect the Lock File

After browsing StackOverflow, many people suggested deleting the lock file:

Terminal window
$ rm package-lock.json
$ npm install
$ git add package-lock.json
$ git commit -m "regenerate lock file"
$ git push
$ vercel --prod
# Failed - Same error!

Attempt 2: Modify Install Command

Tried using npm ci in vercel.json:

{
"installCommand": "npm ci --legacy-peer-deps"
}
Terminal window
$ vercel --prod
# Failed - Failed with the same error

Attempt 3: Suspect a Problematic Dependency

Seeing “Exit handler never called” in the error, I searched and found it might be a postinstall script hanging:

Terminal window
# Check packages with postinstall scripts
$ npm ls --all | grep -i postinstall

Found several packages like sharp, puppeteer with install scripts.

Tried skipping scripts in vercel.json:

{
"installCommand": "npm install --ignore-scripts"
}
Terminal window
$ vercel --prod
# Failed - Install succeeded but build failed (missing sharp binaries)

It’s 7 PM now. I’m starting to question my career choices.

The Turning Point: Comparing with a Working Project

Suddenly remembered that another project deployed successfully last week, also using sharp.

Pulled it down to compare:

Terminal window
# Compare package.json
$ diff project-a/package.json project-b/package.json
# Dependencies are mostly the same
# Compare vercel.json
$ diff project-a/vercel.json project-b/vercel.json
# No significant differences
# Check .npmrc
$ cat .npmrc
# This project doesn't have one
$ cat ~/.npmrc
registry=http://192.168.3.20:4873/

Wait, when did I change the global registry?

Flashback

This 192.168.3.20:4873 is a Verdaccio private npm registry I set up three months ago to speed up npm installs:

Terminal window
# Three months ago...
$ npm install -g verdaccio
$ verdaccio
# Verdaccio running on http://localhost:4873/
# Then I configured the global registry
$ npm config set registry http://192.168.3.20:4873/

Install times went from 2 minutes to 20 seconds. The team loved it. I even shared this “optimization” in our weekly meeting.

But what does this have to do with Vercel deployment?

Bold Hypothesis

If package-lock.json contains private registry URLs
Vercel servers in the US can't access my company's internal network at 192.168.3.20
npm install times out and fails?

Quick verification:

Terminal window
$ grep "192.168.3.20" package-lock.json
# ...tons of output...
"resolved": "http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz",
"resolved": "http://192.168.3.20:4873/@img/colour/-/colour-1.0.0.tgz",
"resolved": "http://192.168.3.20:4873/@shikijs/core/-/core-3.13.0.tgz",
# ...200+ lines...

Holy sh*t! Everything points to internal IPs!

But this is just a guess. I still need to confirm if Vercel is actually trying to request these addresses.

The Smoking Gun: Enabling Debug Logs

To verify my hypothesis, I need to see what npm is actually doing during Vercel deployment.

Modify vercel.json

{
"installCommand": "npm -d install"
}

The -d flag outputs npm debug information, including network requests.

Terminal window
git add vercel.json
git commit -m "debug: add npm debug flag"
git push
vercel --prod

This time the deployment logs finally revealed the key information:

19:56:22.766 Running "install" command: `npm -d install`...
19:56:23.045 npm info using [email protected]
19:56:23.046 npm info using [email protected]
19:56:24.094 npm http cache sharp@http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz 1ms (cache hit)
19:56:24.096 npm http cache @img/colour@http://192.168.3.20:4873/@img/colour/-/colour-1.0.0.tgz 0ms (cache hit)
19:56:24.097 npm http cache @shikijs/langs@http://192.168.3.20:4873/@shikijs/langs/-/langs-3.13.0.tgz 0ms (cache hit)
...
19:56:24.098 npm http fetch GET 200 http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz (attempt 1)
...
19:56:38.234 npm http fetch GET timeout http://192.168.3.20:4873/@shikijs/core/-/core-3.13.0.tgz
19:56:38.421 npm error Exit handler never called!

Case closed!

npm reads the "resolved" field in package-lock.json
Tries to download packages from http://192.168.3.20:4873/
This is an internal IP, unreachable from Vercel servers
Request times out (default 30 seconds)
npm internal state corrupts, "Exit handler never called"
Build fails

Why Does It Work Locally?

Because my computer is on the company network and CAN access 192.168.3.20!

A classic “works on my machine” moment.

Root Cause: npm’s Dependency Resolution Mechanism

The Secret of package-lock.json

I used to think the lock file only recorded version numbers. Actually, it records the complete download URLs:

{
"packages": {
"node_modules/sharp": {
"version": "0.34.4",
"resolved": "http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz",
"integrity": "sha512-xxx..."
}
}
}

npm install Execution Logic

When npm install runs:
1. Reads package-lock.json
2. Downloads packages directly using the "resolved" field's complete URL
3. Verifies using the "integrity" field's hash
4. Only queries the registry if lock file is missing or corrupted
Key point: resolved field priority > registry configuration!

The Contamination Chain

3 months ago: npm config set registry http://192.168.3.20:4873/
At some point ran npm install (maybe when adding new dependencies)
npm resolved all packages from private registry, wrote to package-lock.json's resolved fields
Committed package-lock.json to Git
Vercel clones code, reads lock file
Tries to download packages from internal IP → timeout → failure

The Solution

1. Switch Back to Public Registry

Terminal window
# Check current config
$ npm config get registry
http://192.168.3.20:4873/ # Here's the problem
# Switch to public registry
$ npm config set registry https://registry.npmjs.org/
# Verify
$ npm config get registry
https://registry.npmjs.org/ # Success

2. Clean and Regenerate package-lock.json

Terminal window
# Delete the old one
$ rm -rf node_modules package-lock.json
# Reinstall (now using public registry)
$ npm install
# Verify it's clean
$ grep "192.168.3.20" package-lock.json
# (no output) - Clean!
# Check new resolved fields
$ grep '"resolved"' package-lock.json | head -5
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.13.0.tgz",
# All pointing to public registry now

3. Remove Debug Flag and Deploy

// vercel.json - remove -d flag
{
"buildCommand": "npm run build"
}
Terminal window
git add package-lock.json vercel.json
git commit -m "fix: clean package-lock from private registry"
git push
vercel --prod

Success

19:56:17.848 Installing dependencies...
19:56:23.156 npm http fetch GET 200 https://registry.npmjs.org/sharp 234ms
19:56:24.234 npm http fetch GET 200 https://registry.npmjs.org/@img/colour 89ms
...
19:57:42.123 Build Completed
19:57:43.456 Deployment ready

8:30 PM. Finally deployed successfully. I told the client there was a “temporary Vercel server issue.” They bought it.

Retrospective: If I Could Do It Over

The debugging process should have been

1. Build fails with unclear error
2. Check if reproducible locally
↓ Works locally → environment difference
3. Compare local vs CI environment:
- Node/npm versions (checked)
- Environment variables (checked)
- npm configuration ← Should check here!
- Network access ← And here!
4. Add detailed logs to confirm hypothesis
5. Identify root cause and fix

My detours

  • Blindly trying various configs (Node version, install params, etc.)
  • Prematurely suspecting specific dependencies (sharp, puppeteer)
  • Didn’t check npm config first thing
  • Eventually used detailed logs to pinpoint issue (but should’ve done this earlier)

If I Had Added -d Flag from the Start

// Should have done this after first deployment failure
{
"installCommand": "npm -d install"
}

Would have saved at least 2 hours of debugging!

Prevention Measures

To avoid stepping on this rake again, I implemented these improvements:

1. Never Configure Private Registry Globally

Terminal window
# Dangerous operation - DON'T DO THIS
npm config set registry http://192.168.3.20:4873/
# Correct approach: use scoped configuration
# .npmrc (commit to Git)
registry=https://registry.npmjs.org/
@mycompany:registry=http://192.168.3.20:4873/
# Only @mycompany/* packages use private registry
# Everything else uses public registry

2. Add Git Pre-commit Hook

.husky/pre-commit
#!/bin/sh
if git diff --cached --name-only | grep -q package-lock.json; then
# Check for internal IPs
if grep -qE '192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|localhost|127\.0\.0\.1' package-lock.json; then
echo "ERROR: Private registry detected in package-lock.json"
echo ""
echo "Found internal IP addresses:"
grep -oE '192\.168\.[0-9]+\.[0-9]+|10\.[0-9]+\.[0-9]+\.[0-9]+' package-lock.json | sort -u
echo ""
echo "Fix with:"
echo " npm config set registry https://registry.npmjs.org/"
echo " rm -rf node_modules package-lock.json"
echo " npm install"
exit 1
fi
fi
Terminal window
chmod +x .husky/pre-commit

3. Force Public Registry in CI Config

vercel.json
{
"buildCommand": "npm run build",
"installCommand": "npm config set registry https://registry.npmjs.org && npm ci"
}

4. Team Documentation

Wrote an “npm Private Registry Usage Guidelines” with key points:

  • Only configure private registry in .npmrc (not globally)
  • Use scoped registries for private packages (@mycompany:registry=...)
  • Check package-lock.json before committing
  • Regularly test builds in clean environments

Technical Insights

1. npm Configuration Priority

npm package lookup order:
1. package-lock.json "resolved" field (highest priority!)
2. Project .npmrc
3. User ~/.npmrc
4. Global npm config
5. npm built-in defaults

I had no idea the resolved field had such high priority!

2. package-lock.json Is More Than Version Locking

It also contains:

  • Complete download URLs (resolved)
  • File integrity checksums (integrity)
  • Dependency tree structure (dependencies)

This is why:

  • Commit lock file = fully reproducible builds
  • Don’t commit lock file = “works on my machine”

3. Debugging Techniques

Terminal window
# npm debug logs
npm install -d # Detailed debug info
npm install --verbose # Verbose output
npm install --timing # Performance analysis
# See actual requests
npm install --loglevel=silly 2>&1 | grep "http fetch"
# View configuration
npm config list --json

4. Vercel Debugging Tricks

// vercel.json can override install command
{
"installCommand": "npm -d install", // For debugging
"buildCommand": "npm run build"
}

You can also set environment variables in Vercel Dashboard:

NPM_CONFIG_LOGLEVEL=silly
NPM_CONFIG_REGISTRY=https://registry.npmjs.org/

Final Thoughts

This incident took 3 hours to debug, but gave me a much deeper understanding of the npm ecosystem.

Key Takeaway

When facing “works locally, fails in CI” issues, first thing to do:

  1. Add detailed logging (npm -d install)
  2. Check environment differences (config, network, permissions)
  3. Check package-lock.json resolved fields

Not:

  • Blindly trying various configurations
  • Deleting lock file (might mask the problem)
  • Suspecting specific dependencies

Reader’s Checklist

If your Vercel/CI deployment also has mysterious npm install failures, try this:

Terminal window
# 1. Check npm configuration
npm config get registry
# 2. Check package-lock.json
grep -E '192\.168\.|localhost|127\.0\.0\.1|10\.' package-lock.json
# 3. If private registry URLs found, clean it
npm config set registry https://registry.npmjs.org/
rm -rf node_modules package-lock.json
npm install
# 4. Redeploy
git add package-lock.json
git commit -m "fix: clean lock file"
git push

Hope this article helps anyone facing similar issues!


Related Resources:

Had similar experiences? Share your debugging stories in the comments!

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts

Comments