I changed one line of content on a Next.js site and ran the build.
It hung. Not an error — just nothing. Fifteen minutes of silence. I left it running, did something else, came back, still nothing. My first theory was network: the build step shells out to a CLI, maybe it was reaching out to check something. My second theory was that it was simply slow, because Next.js builds are slow.
Both wrong. And the reason I couldn't tell is worth writing down, because the failure mode is genuinely deceptive.
Silence is not a diagnosis
A build that prints nothing gives you nothing to reason with. A stack trace points somewhere; fifteen blank minutes invites you to invent explanations. I spent the first ten minutes on the network theory before I did the thing I should have done immediately: run the underlying command directly instead of through the wrapper.
My package.json had build:pages running npx -y vercel build && npx -y @cloudflare/next-on-pages --skip-build. Running next build on its own took under two minutes and printed this:
Error: [safe-delete][SAFE_DELETE_BULK_CONFIRM_REQUIRED]
{"count":50,"threshold":50,"scope":"turn", ...}
Then, once I cleared the directory it had been trying to remove:
Error: EEXIST: file already exists, mkdir '.next/server/pages'
at ... /node-brokered-fs-shim.cjs
EEXIST on a directory I had just confirmed did not exist. That's not a slow build. That's a filesystem that isn't behaving like a filesystem.
The actual cause
echo $NODE_OPTIONS
# --require="/Applications/.../node-language-shim.cjs"
My tooling injects a Node module into every node process on this machine, and that module brokers filesystem operations. Its mkdir returns EEXIST for paths that don't exist, and its bulk unlink trips a confirmation guard above 50 files — which a build cleaning .next will hit immediately.
The build wasn't hanging on the network. It was hanging waiting for a delete confirmation that never came.
The fix is one flag:
env -u NODE_OPTIONS npx next build
# completed in ~2 minutes, exit 0
Worth noting what did not work: disabling the sandbox. I tried it, and got the identical error, because the patch arrives through an environment variable, not through process isolation. Same symptom, same cause, no change. Knowing which layer a thing lives in saves you the retry.
The second silence, which was a different problem entirely
With the build working, I re-ran the full pipeline. It hung again — same fifteen minutes of nothing.
This time the cause was almost boring: the wrapper was slow, not stuck. npx -y vercel build spent those fifteen minutes in package resolution. Executing the CLI that was already in the npx cache took nine seconds:
node ~/.npm/_npx/69f9afb961c37556/node_modules/vercel/dist/vc.js build
# Build Completed in .vercel/output [9s]
Nine seconds versus fifteen minutes, for the same work.
So I now had two failures with one symptom — no output — and completely different causes: one was a patched runtime waiting on a prompt, the other was a package manager being slow.
How to tell slow from stuck
That's the part I'd actually keep. When something produces no output:
-
Run the inner command directly. Wrappers swallow errors and add their own failure modes.
next buildtold me in two minutes whatnpm run build:pagesnever said at all. -
Check whether progress is happening somewhere observable. For npx, that's
~/.npm/_npx— no new directory meant it wasn't downloading, which meant it wasn't stuck on the network. - Don't retry the same command hoping for a different result. I nearly re-ran the wrapper a third time. Silence isn't randomness; something specific is happening, and the fix is to go look at it.
The general shape of it: when a tool that always works suddenly doesn't, suspect the environment before the tool. Nothing about Next.js changed that day. Something about how Node was being launched did.
If your build, deploy or data pipeline has started failing quietly and you've run out of theories, that's the kind of thing I do: https://yongrui-services.pages.dev/
Top comments (0)