Skip to content

Make the site discoverable to agents, and serve the docs as markdown - #72

Merged
jnahian merged 3 commits into
mainfrom
feat/web-agent-discoverability
Sep 6, 2026
Merged

jnahian merged 3 commits into
mainfrom
feat/web-agent-discoverability

Conversation

@jnahian

@jnahian jnahian commented Sep 6, 2026

Copy link
Copy Markdown
Owner

The site was crawlable — static HTML, titles, descriptions, canonicals, OG tags — but nothing told a machine what was here. Nothing enumerated the docs pages, nothing typed the claims the prose makes, and an agent that wanted a page had to parse the prose back out of the HTML.

Two commits.

Discoverability

sitemap.xml @astrojs/sitemap, since site was already configured. 18 URLs, 404 excluded.
robots.txt Points at the sitemap.
llms.txt llmstxt.org index, generated from the docs collection.
JSON-LD SoftwareApplication on /, FAQPage on /docs/faq.

Nothing here is a list to maintain by hand. llms.txt is built off the same collection the /docs hub is, so a new page under docs/ lists itself. SoftwareApplication reads softwareVersion from latestVersion, so it tracks releases. A static llms.txt in public/ would have drifted on the next page added, which is what plugins/docs-pages.mjs exists to prevent.

The integration hard-codes <filenameBase>-index.xml, so a build hook renames the index to the /sitemap.xml a crawler actually guesses. Renaming the index rather than the chunk keeps the split intact.

Markdown twins + copy button

/docs/reading is the page, /docs/reading.md is the markdown it was rendered from — all 15 pages, including /docs/faq.md, where the raw ### question headings read better for a machine than the accordion does.

Served byte for byte except the links. Those are on-disk relative paths (../cli.md) so they resolve in Reader.md and on GitHub, and flattening docs/features/ onto /docs/ breaks them — so they're resolved to absolute URLs through the same rewriteLink the rendered page uses, now exported, so a link can't mean one thing in HTML and another in markdown. No markdown parser: stringifying would reflow the prose, and the corpus has no link titles or reference definitions (66 destinations, all inline).

The Copy markdown button on each page fetches its own twin, so the HTML doesn't carry a second copy of the prose. A failed fetch leaves the button alone — claiming "Copied" over an empty clipboard is worse than appearing to do nothing.

The FAQ's answers now reach a reader three ways — accordion, JSON-LD, /docs/faq.md — all derived from docs/faq.md alone.

Verification

Clean rebuild: 15 .md twins, JSON-LD in dist/index.html and dist/docs/faq/index.html, copy button on all 15 docs pages, no unrewritten relative links in the markdown, and the existing href invariant (only absolute GitHub URLs) still holds.

Driven in a browser: 5,469 characters of reading.md on the clipboard, the label and tick through the full cycle, no console errors, FAQ accordion and search unaffected.

Two things worth a look

  • Cloudflare's "Block AI Scrapers and Crawlers" / Bot Fight Mode is a zone setting, not a file in this repo. If it's on, none of this is reachable by the audience it's for.
  • The .md files will serve as text/markdown, which most browsers download rather than display. That's right for agents, so it's left alone. Forcing text/plain needs a _headers rule, and I didn't want to guess whether Cloudflare's wildcard supports a /docs/*.md suffix — if it degraded to /docs/* it would serve all 15 docs pages as plain text. Easy to add and verify once deployed.

web/CLAUDE.md gains a Machine readers section, including the silent failure mode this introduces: the FAQPage JSON-LD comes from rehype-faq-accordion via remarkPluginFrontmatter, and a stale Astro content cache drops it with the build still green. That bit me once while building this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn

jnahian and others added 3 commits September 7, 2026 00:09
The pages were already static HTML with titles, descriptions, canonicals and
OG tags — readable, but with nothing telling a machine what is here. Nothing
enumerated the 16 docs pages, and nothing typed the claims the prose makes.

Four additions, none of them a list to maintain by hand:

- @astrojs/sitemap, since `site` was already configured. 18 URLs, 404 excluded.
  It hard-codes `<filenameBase>-index.xml`, so a build hook renames the index to
  the /sitemap.xml a crawler actually guesses; renaming the index rather than
  the chunk keeps the split intact.
- public/robots.txt, pointing at /sitemap.xml.
- llms.txt (llmstxt.org), generated from the docs collection — so a new page
  under the repo's docs/ lists itself, for the same reason it appears on the
  /docs hub. A static file in public/ would drift on the next page added,
  which is what plugins/docs-pages.mjs exists to prevent.
- JSON-LD, as a `schema` prop on Base.astro passed by the page that knows what
  it is describing: SoftwareApplication from index.astro (softwareVersion off
  latestVersion, so it tracks releases), FAQPage from docs/[...slug].astro.

The FAQ's questions are collected by rehype-faq-accordion as it builds the
accordion and handed over as render()'s remarkPluginFrontmatter, so the visible
answers and the structured ones cannot disagree — 30 questions, no second parse
of the markdown. That coupling has a silent failure mode: a stale Astro content
cache re-emits the old HTML after a plugin edit and the JSON-LD just isn't
there, build still green. Recorded in web/CLAUDE.md under Machine readers and
Verify, alongside the FAQPage schema's assumption that every h3 in docs/faq.md
is a real question.

The <head> description moves to site.ts, which llms.txt needs verbatim too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn
An agent that wants a docs page has to parse the prose back out of the HTML,
and a reader who wants to paste one into an LLM has no way to get it. Both
want the file the page was rendered from.

/docs/reading is the page; /docs/reading.md is its markdown. All 15 pages,
including /docs/faq.md — where the raw `### question` headings are a better
read for a machine than the accordion is.

The body is served byte for byte except for its links. Those are written as
on-disk relative paths (`../cli.md`) so they resolve in Reader.md and on
GitHub, and flattening docs/features/ onto /docs/ breaks them, so they are
resolved to absolute URLs — through the same rewriteLink the rendered page
uses, now exported from remark-docs-assets.mjs, so a link cannot mean one
thing in HTML and another in markdown. A sibling docs page points at that
page's .md, so following a link out of one lands in another. No markdown
parser: stringifying would reflow the prose, and the corpus has no link
titles and no reference definitions (66 destinations, all inline).

The "Copy markdown" button on each page fetches its own twin rather than the
HTML carrying a second copy of the prose. A failed fetch leaves the button
alone — claiming "Copied" over an empty clipboard is worse than appearing to
do nothing.

Verified in a browser: 5,469 characters of reading.md on the clipboard, the
label and the tick agreeing, and the FAQ's 30 `###` headings matching its 30
accordion rows and 30 JSON-LD questions — three renderings of one source file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn
An agent that follows an entry out of llms.txt wants the source, and every
docs page now has a .md twin to give it. The index linked the HTML, which
sent it back to parsing prose out of markup.

The Optional section keeps its HTML links: the home page and the changelog
have no .md twin, and Source is the repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGMQ49hvA936om3GQfNTAn
@jnahian
jnahian merged commit a09dbe6 into main Sep 6, 2026
1 check passed
@jnahian
jnahian deleted the feat/web-agent-discoverability branch September 6, 2026 19:01
@jnahian jnahian added the web The marketing site under web/ (and the docs pages it renders) label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

web The marketing site under web/ (and the docs pages it renders)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant