Skip to content

Vercel Deployed, but Your Domain Still Serves the Old Version

The deployment exists, the dashboard says Production, promote says 'already current' — and the apex domain is serving a build from an hour ago. We have logged this repeatedly in production. One command fixes it; the verification routine is what stops it from lying to you again.

August 20, 20267 min readShift The Culture

vercel --prod reports Ready. The dashboard shows the deployment as Production. And your custom domain is still serving the previous build — new pages 404 on the real domain while returning 200 on the *.vercel.app deployment URL. We have hit this repeatedly on a CLI-deployed production site, logged every occurrence, and the fix is one command — but the deeper problem is that every signal you would naturally check says the deploy worked. Here is the fix, and the verification routine that stops this class of failure from lying to you.

What is actually happening

A deployment and your domain are separate objects. Deploying creates an immutable build at a unique URL; your custom domain is an alias pointed at some deployment. In the happy path a production deploy moves the alias automatically. In practice, on our CLI-deployed project (no git integration), we have logged multiple sessions where it did not: the new deployment existed and was marked Production, vercel promoteeven returned 409 “already the current production deployment” — and the apex domain kept serving a build from 44 minutes to several hours earlier, with response headers showing x-vercel-cache: HIT and a large age. Polling did not fix it; twenty minutes of waiting did not fix it.

We have not root-caused which project configurations auto-move the alias and which do not — call that part INFERRED. What is not inferred: the failure exists, it is silent, and the explicit command below has moved the domain correctly every single time.

The fix

deploy, then move the domain explicitly
url=$(vercel deploy --prod --yes | tail -1)   # capture the deployment URL
vercel alias set "$url" yourdomain.com        # point the domain at it, explicitly

vercel alias set is idempotent — if the domain already points at that deployment it succeeds harmlessly. We run it after every production deploy, unconditionally. The cost is one command; the alternative is discovering at some later date that customers have been on the old build.

Verification that actually discriminates

This is the part most write-ups skip. All of the following are signals we have watched report success while the domain served stale content — none of them verify anything:

  • The deploy command's own output (Ready, Environment: Production).
  • vercel ls showing the deployment as Production.
  • vercel promotereturning “already the current production deployment”.
  • An HTTP 200 from the domain. A 200 only proves something answered — the old build also returns 200. Worse, a 200 can be a redirect to a password page or an error shell if you did not follow redirects.

The routine that does discriminate, in three steps:

  1. Fetch the real domain, not the deployment URL, with a cache-busting query string: curl -sL "https://yourdomain.com/new-page?cb=$(date +%s)". The deployment URL always has the new build — it proves nothing about the domain.
  2. Grep for a content marker that only exists in the new build— a new headline, a build stamp, the new page's title. HTTP status is not content; after one rollback we recorded “re-verified 200” while the content had never changed.
  3. Run a fabricated control.Request a slug that does not exist and confirm it 404s. If your fake page “succeeds” too, your check is not measuring what you think — a catch-all route, a soft-404, or an over-eager CDN is answering everything, and step 2 was worthless.
the whole receipt in four lines
curl -s -o /dev/null -w "%{http_code}" "https://yourdomain.com/new-page?cb=$RANDOM"   # expect 200
curl -sL "https://yourdomain.com/new-page?cb=$RANDOM" | grep -c "MARKER_ONLY_IN_NEW_BUILD" # expect >=1
curl -s -o /dev/null -w "%{http_code}" "https://yourdomain.com/zz-fabricated-$RANDOM"      # expect 404
curl -sI "https://yourdomain.com/?cb=$RANDOM" | grep -i x-vercel-id                        # confirm edge answered

If an agent does your deploys

This trap is nastier when a coding agent runs the deploy, because the agent reads Ready and reports success in confident prose. Ours did, more than once, until we encoded the rule into the deploy procedure itself: the deploy is not done until the alias is set and the three-step receipt above passes on the real domain. Procedure beats memory — for agents and for tired humans at 1 a.m. If your automation breaks in ways that only surface in production, the broader pattern is covered in why AI automations break in production.

The deploy receipt, on one screen

every production deploy, no exceptions
1. vercel deploy --prod --yes         → capture the deployment URL
2. vercel alias set <url> <domain>    → move the domain explicitly, every time
3. curl the REAL domain + cache-buster, grep a new-build marker
4. curl a fabricated slug             → must 404 (proves the check discriminates)
5. only now write "live" anywhere

Vercel's own reference for the command: vercel alias. If this page saved you the afternoon it cost us, the sibling failure — a package registry serving your old README while GitHub shows the new one — is written up in npm README not updating.

SharePost on X
Free · 13 pages · no upsell inside

Get the Operator Field Kit — free

Six production prompts, the five-step operator setup, and nine rules from our own failure log.

  • 6 complete prompts — printed in full, not previews
  • The five-step setup, each step with a pass/fail test
  • 9 rules from the failure log that produced them

The kit, then the occasional operator note. One click unsubscribes and we never sell the address.

Keep reading