DEV Community

Talha Ramzan
Talha Ramzan

Posted on

The Deployment Failure That Only Shows Up on a Clean Clone

Six months of adding tools to my site, one page router file, and a
lesson about the difference between "works on my machine" and "exists
in version control."

The setup

DukoTools just went from 112 to 138 tools. Most of that work happened
gradually, build a component, wire it into the router, test locally,
move to the next tool. Standard incremental development.

What I didn't do consistently: commit each tool as a complete, atomic
unit the moment it was wired in.

The bug

While fixing an unrelated i18n issue, I edited the shared tool page
router, src/app/[locale]/tools/[slug]/page.tsx ,to add hreflang
gating logic. The edit was correct. The file compiled locally. I
committed it.

The next Vercel deploy failed:

The router file already had import statements and switch cases for
7 tools whose actual component files had never been committed to git.
They existed only on my local machine. Locally, the build succeeded
because the files were sitting right there on disk. On a clean Vercel
clone, they didn't exist, and the build died at the first missing import.

I hadn't noticed because I was editing that file for a completely
unrelated reason. The broken references were already there before I
touched it, my edit just happened to be the commit that finally
shipped them to main.

The actual root cause

The router file was being treated as a single artifact. In reality,
it's a shared surface that multiple, independently-completed
features all touch. Every tool added an import line and a switch case
to the same file, but the component itself, its tools.ts entry, and
its translations were separate, independently-committable units that
didn't move in lockstep.

Nothing enforced that "this router line" and "this component file"
ship together. So eventually they didn't.

The fix, and the verification that mattered more than the fix

The fix itself was small: remove the 14 orphaned lines (7 imports + 7
switch cases) referencing uncommitted components.

The verification is the part worth stealing:

git stash push -- page.tsx  # revert to committed state
npm run build                # does it fail the same way without my changes?
git stash pop                 # restore
Enter fullscreen mode Exit fullscreen mode

Confirming the bug existed before my edit — not because of it —
mattered more than the fix itself. It's the difference between "I
broke this" and "I finally exposed something that was already broken."

The stronger verification, once the fix was ready:

git clone <repo> /tmp/clean-test --branch fix-branch
cd /tmp/clean-test
npm install
npm run build   # exit code 0, in an environment with zero local state
Enter fullscreen mode Exit fullscreen mode

A fresh clone into a temp directory, with no leftover node_modules,
no stray local files, nothing my dev machine happened to have lying
around, the closest local simulation of what Vercel's build
environment actually sees. npm run build passing locally never
proved that. Only a clean clone does.

What generalizes

  • A shared file (router, config, registry) is only as reliable as the discipline around what's allowed to reference what. If feature A can add a line to a shared file before feature A is fully shipped, you've built a landmine, not a bug.
  • "Builds locally" and "builds from a clean clone" are different claims. Local builds see your entire filesystem, including whatever you forgot you had lying around. CI/deploy only sees git.
  • Before fixing a bug, prove it predates your change. git stash, rebuild, compare. It's five extra minutes and it's the difference between confidently shipping a fix and guessing you didn't make things worse.
  • Ship features as atomic units across every file they touch — component, registration, content, translations together, or not at all. Partial commits of a multi-file feature are how this bug is born.

Status

138 tools, live, verified via clean-clone build. The 7 previously-
orphaned tools are now fully committed as complete units, component,
registration, and content shipped together in one commit, not spread
across a week of half-finished edits.


Building DukoTools solo — 138 free tools and counting, apparently
also collecting deployment lessons at a similar pace.

Top comments (1)

Collapse
 
raknaos profile image
Raknaos

"Builds locally" and "builds from a clean clone" being different claims — because the local build gets to read your entire filesystem — is the sentence worth pinning in every repo. The git stash push -- page.tsx then rebuild step is what I'd steal: proving a broken reference predates your edit turns a panic into a five-minute check, and it also tells you the shared file was already a landmine before you touched it.

The root cause generalises way past routers. We keep arriving at the same class of bug through different doors: a config entry pointing at a module that never shipped, a migration referencing a column added in an unmerged branch, a plugin registered in a manifest whose file only exists on one laptop. Did you end up automating the clean-clone build as a CI gate, or is it still a manual check before merge? And did anything change in how you order the commits now — component first, registration last?