For the first three weeks this site was live, none of its section anchors existed in the HTML it served. The table of contents on every post worked fine in a browser, because a script gave each heading an id after the page loaded. A crawler that doesn't run JavaScript got headings with no ids, and a link to #why-the-top-ranking-stopped-guaranteeing-the-citation pointed at nothing.
I fixed it on 26 August by moving the ids into the build. The fix was small. The rule it left behind runs the whole site now: anything a machine reads is generated from the page a person reads, at build time, never written a second time by hand.
The stack is deliberately boring
The site is Eleventy 2, plain HTML and one stylesheet. Home, About, Work, Tools and Contact are hand-built pages that Eleventy copies through untouched. Posts, frameworks and studies are Markdown files rendered through three shared layouts.
I write in Sveltia CMS at /admin/. It logs in with GitHub through a small Cloudflare Worker, and publishing a post commits straight to main. A GitHub Actions workflow builds the site and force-pushes the output to a deploy branch, which Hostinger serves. From pressing publish to the page being live takes about a minute.
There is no framework on the front end and no bundler. site.js is one file that checks whether each feature's element exists before it does anything, so the same script runs on every page.
Every machine layer is generated from the human page
None of these layers is a confirmed citation lever, and in my own audits I rank files like llms.txt as hygiene, not strategy. They're here because they're cheap, and because generating them from the built HTML, never from the source, keeps the machine version honest:
| Layer | What generates it | What it reads from |
|---|---|---|
| Markdown twin of every page | scripts/markdown-twins.js |
The rendered <main> of each page |
answers.json |
The same script | Every direct-answer block on the site |
| Content-Security-Policy | scripts/csp.js |
Every inline script in the built HTML, hashed |
Sitemap lastmod |
An Eleventy filter | The last git commit that touched each source file |
| The "machine view" panel on About | site.js, in the browser |
That page's own JSON-LD |
The twins are the clearest example. On the 2 October build, the homepage was 71,835 bytes of HTML and 11,219 bytes as Markdown, the same content at 84% fewer bytes for an agent that asks for text/markdown. Because the twin is converted from the rendered page, I can't update one and forget the other.
The CSP works the same way. Instead of allowing 'unsafe-inline', the build hashes each inline script it finds, 7 distinct hashes across 34 blocks on the 2 October build, and writes a policy that allows exactly those. A script I add tomorrow is allowed on the next build; one I delete stops being allowed.
The sitemap dates come from git rather than from the build clock, so a page only looks fresh to a crawler when its content changed. Rebuilding the site to fix a typo on one post doesn't tell Google that every page is new.
What I took out
Two things I built in September didn't survive the month. On 10 September I added a provenance registry, a page tracing every published number to its source, and removed it on 11 September. The same day I added a topical map page, with layer badges on every post; it came out on 18 September. Both came out within eight days of going in, and the site is simpler for it.
The homepage figures used to count up from zero as you scrolled to them. That meant every visitor saw a smaller number than the real one for the first second. They're printed once, server-side, now.
One regression turned up while I was writing this post. The retrieval animation I added to the homepage dims each step until it lights up, and I dimmed it with opacity. Lighthouse flagged five descriptions under the contrast threshold before they lit, and accessibility dropped to 96. Dimming by colour instead keeps the effect and passes.
What Lighthouse reported
These are Lighthouse 12.8.2 runs on 2 October 2026 against the production build, served locally rather than from Hostinger, so the server headers and CDN are not in them. Treat performance as indicative; accessibility and SEO don't depend on the server.
Mobile LCP was 2.8 seconds, desktop 0.7, with zero layout shift and zero blocking time on both. Accessibility is 100 on every page I changed this week, and an axe-core pass on 17 September found zero WCAG 2.2 AA violations across all 27 public pages.
Two categories land below 100, and I'm leaving both. Best practices loses four points for console errors, and both are Google Fonts and the Cloudflare analytics beacon failing to load inside my test sandbox, which blocks outside requests. SEO loses eight because Lighthouse doesn't recognise the Content-Signal lines in robots.txt. Those lines are deliberate: they tell AI crawlers this site allows search, AI input and training. I'd rather keep the signal than the eight points.
Where this is overkill
If your site is three pages and you don't publish, most of this is wasted effort. A clean static page with a Person entity in its JSON-LD gets you most of the way, and markdown twins add nothing when there's nothing to read.
I also can't tell you yet how often agents use the twins. Cloudflare Web Analytics runs as a script in the page, and a request for index.md never runs that script, so the analytics on this site can't see those fetches at all. Server logs would be the place to look.
Read next
The LLM Technical SEO Checklist: Seven Stages From Crawler Access to Citation
A technical SEO checklist for AI search: seven stages in order, every check with a pass rule and a P0 to P2 priority, backed by sources and run on a live site.
Read the post →How to Get Your Brand Cited by ChatGPT, Gemini and Perplexity
Ranking #1 in Google no longer guarantees you appear in an AI answer — the systems judge content differently. A checklist to make pages retrieval-ready.
Read the post →