Skip to content

Four Ways a Site Launch Lies to You

CONVERSION TRACKING
Four Ways a Site Launch Lies to You
Conner Crowe

I put a client’s new site live last week and it was broken for about an hour. Every page loaded. The design was right, the content was right, the redirects were right, and the thing rendered perfectly.

Underneath, the site search told every visitor it was unavailable, two files the site links to from its own pages returned 404, and a build variable that the bundler never saw had left a feature on the page inert.

I caught it, fixed it at 00:56, and then wrote a deploy script so it can’t happen again. Four separate failures fired that night. None of them is exotic, and all four are invisible from the browser.

One: the build command that skipped half the build

The build was made with npx astro build.

That runs the framework and nothing else. The prebuild and postbuild steps in package.json only run when the build is started through npm. On this site, every generated output is written in postbuild: the search index, the machine-readable files, the redirect markers.

So the build succeeded, produced a directory full of correct-looking pages, and silently omitted everything that wasn’t a page.

The generalized version of this one isn’t about Astro. Any modern site has a build pipeline with steps hanging off it, and calling the framework directly instead of the project’s own build command skips them all without a warning. If your repository defines npm run build, that is the build. Reaching past it to the tool underneath is how you ship three quarters of a site.

Two: an environment variable that was set, and wasn’t

The worst consequence that night came from a single build variable left unresolved, which meant a component that depends on it rendered nothing, and a feature built on that component sat inert on an otherwise perfect-looking page.

I had set that variable in the shell. The shell reported it as set. The build never saw it.

I still don’t have a complete account of why, and I’d rather say that than invent one. Vite documents process.env as winning over the .env files, so setting it in the shell should have been enough, and on this build it wasn’t. What I could establish is the ordering that fixed it: Vite loads .env, then .env.local, then .env.[mode], then .env.[mode].local, each overriding the last. Moving the value into .env.production put it above the stale one in .env.local and the build picked it up. The comment inside .env.local telling you to use a shell variable is wrong for this project whatever the mechanism turns out to be.

The lesson worth keeping: echo $VAR only ever reports on your shell. If a variable matters, assert on the built output, by grepping the artifact for the compiled value, rather than on the environment you built from.

I wrote up the pre-campaign version of this check, where the flag was wrong on an ad destination rather than a deploy, in The Preview Flag.

Three: a fresh terminal that built a different site

Environment variables are session-local, and I opened a new terminal.

That build came out with the wrong build flags, which meant it declared itself noindex and generated no sitemap. It also got published.

A site that tells search engines not to index it looks completely normal to a human. There’s no visual difference, no error, no warning in the deploy log. You find out weeks later, when the traffic doesn’t come back after a migration.

Four: the host rebuilt it and undid everything

The deploy went out with a plain deploy --prod. The hosting project has its own build command configured, so the host ignored the artifact I’d just built and rebuilt the site itself, without any of the variables that were only ever set on my machine. It then published that.

This is the trap I would most expect to catch somebody who has done everything else right. You can build correctly, check the artifact carefully, and still have the host throw it away and substitute its own. The deploy has to be told to publish the directory as-is.

What replaced all of it

One script, and nothing else is allowed to deploy.

It sets the variables, runs the full npm lifecycle rather than the framework directly, checks the artifact before it goes anywhere, deploys with the build step disabled so the host publishes exactly what was checked, and then verifies the live site afterwards.

A deploy isn’t finished when the upload succeeds. It’s finished when the live domain has been asked whether the thing you expected is there. The script checks status codes on the pages that matter, samples the legacy redirects, confirms the generated files exist rather than 404, confirms the site declares itself indexable, and confirms the mail records still resolve to the mail host, because the one failure that would genuinely hurt this client is the one nobody would notice for a day.

Ten checks against the live domain, seconds to run, every time.

The pattern under all four

Each of these failures produced a site that rendered perfectly, which is why they survive a visual review.

The four questions I would now ask of any launch, in order:

  1. Did the build run through the project’s own build command, or did somebody call the tool underneath it?
  2. Are the environment variables present in the compiled output, rather than in the shell that started it?
  3. Does the live site declare itself indexable, and does its sitemap exist?
  4. Did the host publish the artifact you checked, or did it build its own?

I wrote separately about the fifth question, which is whether the redirects preserve the parameters your advertising depends on, over at A 200 Is Not Proof. Same family of problem. The page loads, so everyone stops looking.

The full account of this particular migration, including what I haven’t claimed about it yet, is in the case study.

Want a review like this on your account?

Want this kind of review
on your account?

Thirty minutes on the phone. Same person on the call as on the work. Walk out with a clear set of next steps.