A field note from the AIdeazz AI Lab — a real incident on a live production system, written up from the logs. August 28, 2026.
A single-page app prerendered static HTML so AI crawlers could read its money pages. It worked for the crawlers that do not execute JavaScript, was discarded entirely by the one that does, and the site's own audit scored the discarded half A+ 100/100.
What it looked like from outside
The portfolio page -- the commercial page the whole site funnels toward -- appeared to have fallen out of Google, while the homepage and two other pages ranked normally. The page returned HTTP 200, was listed in the sitemap, carried a self-referencing canonical and index-follow, and no URL variant split its identity. The site's own AI visibility audit scored it A+ 100/100 with zero non-passing checks. Every instrument said the page was healthy and something external had gone wrong.
What was actually happening
The page had two identities and they disagreed. A post-build step wrote standalone static HTML for each money page -- a commercial title plus a roughly 650-word crawler article -- and placed that article inside a noscript block. The client-side application then set document.title, meta description and canonical on mount from values hardcoded in its own page component. Google executes JavaScript, so on render the noscript article was discarded by specification and the runtime values overwrote the prerendered head. The prerendered identity was therefore never indexed by Google at all, while non-executing AI crawlers read only that half and never saw the runtime one. Neither file was wrong; nothing threw; nothing was logged. The split was invisible because each surface looked correct when inspected on its own, and the internal audit read raw HTML -- the same half the AI crawlers read, and the opposite half from the one Google stores.
The fix
Proved the direction of the failure before changing anything, by string-matching the search index against the source. Then collapsed the two definitions into one JSON file that both the build-time prerender script and the client-side page component read, so the two surfaces cannot drift again, with the commercial title as the surviving value. Deliberately excluded the audit API page from the change: its runtime title and description were the ones already ranking, so unifying it would have altered a page that was working. Scoped the edit to head identity only -- no markup changed, so nothing moved visually on the live page.
How I know it worked
Google's stored title for the portfolio page matched the client component character for character, not the prerendered file; the same held for the audit page against its own component, confirming the pattern was systemic rather than a one-off. The page was never deindexed -- it ranked fourth in a site query returning 131 indexed pages, and fifth for its own name query, behind the author's own social profiles. Absence from brand queries was host crowding, roughly two results per domain, not a defect. After deploy, checked against the served bytes rather than the commit: the live JavaScript bundle contained the new title once and the old title zero times. The untouched page was byte-identical except the build's own date stamp, its ranking title still present in the live bundle; the static SOP page and the homepage were unchanged; all six money pages returned 200.
The rule this earned
Decide which representation your most important consumer actually stores, and verify against that copy rather than the file you shipped. When a static build and a client-side application both set head identity, they are two sources of truth and the one that executes last silently wins -- so they must read one file. A noscript block is a fallback for absent JavaScript, not a channel to a crawler that runs it. And know which half of the audience your own checker reads: a raw-HTML scorer measures the non-executing consumers, which is a real audience and a real number, but it is not a verdict about a rendering one. A perfect score on content the primary reader discards is not a good result, it is an instrument answering a neighbouring question.
The named concepts behind it
Naming a failure mode is what makes it possible to recognise the same shape somewhere new, before it costs another weekend.
The render is the artifact
What a JavaScript-executing consumer stores is the DOM after execution, not the HTML you served. Anything that exists only before execution was never delivered.
This is one step past git is not the origin. There the receipt lied about what was being served. Here the serving is genuinely correct and current -- and still the consumer stores something else, because it transforms your bytes before reading them.
Modern search crawlers execute JavaScript. The thing that lands in the index is the DOM after that execution, not the file that came off the wire. Two consequences follow, and both are counter-intuitive because the served file looks perfect in curl and in the browser's View Source.
First, content that exists only in the pre-execution HTML is invisible. is the sharpest example: by specification its contents render only when scripting is disabled, so a crawler running with JavaScript on discards it. A block is a fallback for the absence of JavaScript. It is not a crawler channel, even though a crawler that does not execute JavaScript will happily read it -- which is exactly why the technique appears to work when you test it with a plain fetch.
Second, anything the application rewrites at runtime wins. If the page ships a carefully-chosen and then the client-side router sets document.title on mount, the served title never reaches the index. Both values are "correct" in their own file. Nobody wrote a bug. The two simply describe the same page differently, and the one that executes last is the one that counts.
The result is a split audience. Consumers that execute JavaScript see one page; consumers that do not -- many AI crawlers, link-preview bots, plain HTTP clients -- see the other. Optimising for one can silently be measured with a tool that reads the other, which is how a page earns a perfect score on precisely the content its most important reader throws away.
Ordinary-life version: you post a letter with a covering note clipped to the front. The recipient's mailroom removes every clip before delivery. Your letter arrived. Your note never existed, as far as the reader is concerned -- and photographing the envelope on your desk will never reveal that.
Defences:
1. Diff the two representations deliberately. Fetch the URL raw, then fetch it through a renderer, and compare title, description, canonical and word count. If they disagree, decide which one you meant -- do not let execution order decide for you.
2. One definition, both surfaces. If a static build writes head identity and the client also sets it, both must read the same source file. See single source of truth.
3. Verify from the consumer's stored copy, not your own. The decisive evidence is what the index actually holds. String-match it against your source: whichever file it matches character for character is the one that is really shipping.
4. Know which half your instrument reads. A checker that fetches raw HTML measures the non-executing audience. That is a real audience and a real score -- but it is not a verdict about a rendering one.
Single source of truth
Copy logic instead of calling it, and you have scheduled a bug for a date nobody will tell you about.
When the same rule, prompt, threshold or piece of logic exists in more than one place, the copies begin identical and end different. Nothing announces the divergence. Someone updates one copy, the others keep running the old behaviour, and the system's actual conduct is now split across versions that no single file describes.
The failure is especially nasty when a copy lives somewhere code review cannot see it: a hosted workflow builder, a dashboard setting, a scheduled job on one machine, a prompt pasted into a vendor interface. Those copies never appear in a diff, so the drift stays invisible until it produces a visibly wrong result in front of a customer.
Two defences that work:
- One definition, imported everywhere. Every consumer reads the same file. Where a copy must physically live elsewhere, generate and push it from that file rather than editing it by hand.
- Detect drift automatically. Re-read the remote copies on a schedule and raise an alert when one no longer matches the source. A copy you cannot diff is a copy you must monitor.
Verify from logs, not config
Configuration tells you what somebody intended. Logs tell you what happened.
A setting, an environment variable or a present API key is a statement of intent. It is evidence that somebody meant for a behaviour to occur. It is not evidence that the behaviour occurs.
The gap between the two is where the longest outages live, because reading the configuration feels like verification. It produces confident, wrong statements: the key is set, so the provider works; the schedule says every fifteen minutes, so it runs every fifteen minutes; the file was deployed, so the new code is running.
Each of those has a cheap, decisive check that costs seconds:
- Probe the dependency, do not read its credential. A key that exists proves nothing about the balance behind it.
- Grep for the action line, not the setup line. A startup banner proves the process started, not that it ever did its work.
- Compare timestamps after a deploy. If the running process is older than the file on disk, it is still executing the previous version from memory.
The rule this earns: never report a system's behaviour from its configuration. Grep the line that proves the behaviour happened, and quote it.
Silent failure
The system did something reasonable, and told nobody.
The most expensive bug class there is, because the clock keeps running while everyone assumes things are fine.
A silent failure is not a crash. A crash is loud and gets fixed. A silent failure is a component making a defensible local decision -- drop this message, skip this record, return an empty string -- that nobody downstream is told about. From the outside, a system that is working perfectly and a system that is completely dead can produce the identical observation: nothing happened.
The defence is not "add more logging". It is to make the healthy state provable, so that "nothing happened" can be distinguished from "nothing was supposed to happen". Two things do that:
- Log the outcome, not the attempt. "sending notification" tells you nothing. "notification DELIVERED (id 4661)" versus "notification REJECTED 400" tells you everything.
- Run a canary. A synthetic transaction pushed through the real path on a schedule, which shouts when it does not come out the far end. Without one, you are relying on a customer to report your outage.
---
This note is one entry in a running wiki of production engineering lessons — every concept linked to the incident that taught it — at aideazz.xyz/ai-ops-wiki.html.
No customer data, credentials, hostnames or internal record identifiers appear in these write-ups.