Migrating a Site That AI Assistants Already Cite
AI assistants that already cite URLs on your domain hold a stale index and give no warning when a citation breaks. The exposure most 2026 migrations miss.
I put a client’s new site live in September 2026 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.
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.
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.
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.
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.
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.
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:
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 tracking layers a launch can break are mapped in the Tracking Stack framework.
The full account of this particular migration, including what I haven’t claimed about it yet, is in the case study.
The launch checks above are yours to run. The ad account that will send traffic to the new site is the part I check for free: send me read-only access and I’ll write up what its tracking and campaigns are really reporting, back within 24 hours. No call.
AI assistants that already cite URLs on your domain hold a stale index and give no warning when a citation breaks. The exposure most 2026 migrations miss.
Four page paths in my analytics project have never existed on my site. A pitch mockup carried the same public key. The provenance check that finds it.
Add me as a read-only user and I'll write the audit myself: 5 to 7 pages, back within 24 hours of access. A home or furniture store can send the store link instead. Rather talk first? A call is open too.