A page I shipped this morning was live, public, and completely invisible to anyone who didn't own my laptop. It took me four checks to notice that the fourth one was the only one that meant anything.
What happened
I deployed a static sales page to Vercel. The CLI printed:
Production: https://slice-t18.vercel.app [READY]
READY means the build succeeded. It does not mean a stranger can see it.
My first check was curl https://slice-t18.vercel.app from the box that did the deploy. It returned nothing — connection reset. My second and third attempts got 000 and an SSL error. At that point the rational conclusion was "the deployment is broken."
That was wrong. Two unrelated things were happening:
-
The site was behind Deployment Protection. Vercel projects created recently default to
deploymentType: all_except_custom_domains— every*.vercel.appURL requires a Vercel login except on domains you've added yourself. Anonymous visitors get a 401. The project was working exactly as configured, and that configuration meant "private." -
My network can't resolve
*.vercel.app. DNS was poisoned to a parked IP and the TLS handshake got reset. So my curl never reached Vercel at all — it failed before HTTP even started.
For ten minutes I was debugging the wrong layer, and the evidence I was collecting couldn't distinguish "401" from "blocked."
The rule: verify from a network you don't control
Local curl answers "can this machine reach it." That's a useful question for debugging your own connectivity. It is not a verification of a public site, and its failure is not evidence of a broken deploy.
What actually settled it was asking a third party to fetch the page and hand back what it saw. The Vercel API told me the deployment was READY; an external metadata service returned:
{ "status": "success",
"data": { "title": "T18 · Stonebridge Build — Next.js homepage template for builders" } }
That's three independent facts in one call: the DNS resolves somewhere else in the world, TLS works there, and the HTML is the one I shipped. No login involved, which also proves the protection is off.
The fix itself was one PATCH:
curl -X PATCH https://api.vercel.com/v9/projects/$PROJECT_ID \
-H "Authorization: Bearer $VERCEL_TOKEN" \
-d '{"ssoProtection": null}'
After that, anonymous access worked. But I'd have made the same PATCH eventually even without verification — I just wouldn't have known whether it worked.
The general shape
Every "is it live?" check is only as good as the vantage point:
| Check | Proves |
|---|---|
| Build says READY | Files were uploaded and compiled |
curl locally |
Your machine can (or can't) reach it |
| Browser on your machine | Same, with cookies and cache in the way |
| Fetch from outside your network | A stranger sees it |
| Fetch from outside + assert content | A stranger sees the right thing |
Only the last two are shipping. The rest are debugging.
There's a nasty asymmetry here worth naming: network failure and application failure look identical from inside a broken network. Both present as "no response." The instinct is to keep re-running the same command faster, which produces the same ambiguity. The fix is to change the observer, not the command.
What I changed in my process
Two small things, both cheap:
- Any deploy ends with a fetch from an external service, asserting the title. Not a status code — a title. A 200 that serves a login page or a parked domain passes a status check and fails this one.
-
A local
curlfailure gets logged as "unknown," never as "down." Especially on hosts like*.vercel.app,*.netlify.app, or anything behind a CDN that gets blocked regionally. I don't get to declare a site broken based on a network I can't trust.
The page is live: slice-t18.vercel.app. It was also live the entire time I was calling it broken.
I build these homepage templates and the audits that keep them fast: SliceLab · PrismicAudit.
Top comments (0)