llms.txt Link Hygiene
The structure of an llms.txt file gets reviewed; the links inside it usually do not. We pulled 17 production files on October 19, 2026 and looked at all 2,768 URLs in them — how they are written, where they point, and which of them a fetcher would fail on. The answers split cleanly: almost everyone uses absolute URLs, three quarters point at Markdown twins, and the failures cluster in one predictable place.
What 17 Live Files Do With Their Links
Fetched with a normal desktop user agent, redirects followed, bodies parsed for Markdown links. Two
hosts answered without a file — astro.build/llms.txt returned 404 and
tailwindcss.com/llms.txt returned 404 with an HTML body — so 17 of 19 candidates were
usable. Totals across those 17: 394,235 bytes and 2,768 links.
| File | Links | Absolute | Root-relative | .md | Trailing / |
|---|---|---|---|---|---|
| docs.stripe.com | 527 | 527 | 0 | 516 | 0 |
| docs.anthropic.com | 784 | 784 | 0 | 783 | 0 |
| nuxt.com | 342 | 342 | 0 | 337 | 0 |
| bun.sh | 322 | 322 | 0 | 319 | 0 |
| docs.github.com | 117 | 117 | 0 | 0 | 0 |
| developers.cloudflare.com | 112 | 112 | 0 | 0 | 0 |
| ui.shadcn.com | 111 | 111 | 0 | 0 | 0 |
| hono.dev | 94 | 94 | 0 | 0 | 0 |
| www.checklyhq.com | 68 | 68 | 0 | 1 | 59 |
| www.prisma.io | 64 | 64 | 0 | 29 | 1 |
| resend.com | 58 | 58 | 0 | 25 | 0 |
| vite.dev | 49 | 6 | 43 | 47 | 0 |
| supabase.com | 42 | 42 | 0 | 30 | 0 |
| www.mintlify.com | 41 | 41 | 0 | 35 | 0 |
| vercel.com | 27 | 27 | 0 | 20 | 0 |
| svelte.dev | 7 | 7 | 0 | 0 | 0 |
| llmstxt.org | 3 | 3 | 0 | 3 | 0 |
Three independent decisions sit in those columns — URL form, Markdown twin or HTML page, trailing slash — and only the first is near-unanimous.
Absolute or Root-Relative: One File Buys the Risk
2,725 of 2,768 links are absolute, 98.4%. The single exception is vite.dev, where 43 of 49
links are written as site-root paths such as /guide/features.md, and only the two external
references to developer.mozilla.org and rolldown.rs carry a host. That works
because the file sits at the domain root, so every path resolves back to vite.dev. Six of
its twins we sampled answered 200.
The trap is what happens when the same convention moves into a subdirectory. A relative path in Markdown is resolved against the containing document's URL, and the answer differs by one directory level:
# file at https://example.com/llms.txt
# link: ./guide/install.md → https://example.com/guide/install.md (correct)
# file at https://example.com/docs/llms.txt
# link: ./guide/install.md → https://example.com/docs/guide/install.md (correct)
# link: /guide/install.md → https://example.com/guide/install.md (silently wrong)
Both parse; the failure is silent. If your
index is one of several — the spec allows /docs/llms.txt alongside the root file — that
ambiguity costs you nothing to remove. Absolute URLs also survive the case where an agent has cached the
file but resolves links against a different base, which is what happens when a proxy or a mirror serves
your content under its own hostname.
The .md Twin Majority, and the Index Pages That 404
2,145 links (77.5%) end in .md. Nine of the 17 files are almost entirely twins — Stripe at
516 of 527, Anthropic at 783 of 784, Nuxt at 337 of 342 — while five files link none at all:
Cloudflare, GitHub, Svelte, Hono and shadcn all point at HTML pages only. Both choices are defensible;
the twin is smaller and cleaner, the HTML page is guaranteed to exist.
We sampled 12 twins across 11 files and 11 returned 200. The content type was
text/markdown; charset=utf-8 in ten cases and text/plain; charset=utf-8 for
Prisma's /index.md. The one failure is instructive: bun.com/docs/index.md
returned 404 with an HTML body, because an index.md is a directory of links rather than a
document. In a 12-link sample from bun's file, the two section-index twins —
/docs/index.md and /docs/runtime/index.md — both 404'd while the ten leaf
pages served normally. Section indexes are the twin that generators forget to emit.
One more property of twins worth knowing: the same twin URL is not always the same document. Stripe's
/sandboxes.md sends Vary: Accept, Accept-Language and returns
# Sandboxes (3,731 bytes) to an en-US client, # サンドボックス
(2,463 bytes) to ja-JP, and # 沙盒 (3,560 bytes) to zh-CN. That is
correct behaviour for a localized site, and it means an English llms.txt entry can hand an agent a
Japanese body if the agent sends a matching language header. Point entries at the localized path when you
have one.
Trailing Slashes Are an HTML Signal
Checkly's file is the outlier on URL shape: 59 of its 68 links end in a slash, and the six we sampled
all returned 200 as HTML. Its only .md link is /pricing.md. This is coherent,
not sloppy — the site publishes no Markdown twins, so every entry is a page — but it matters for anyone
copying the pattern. A trailing slash and a .md suffix describe different resources, and a
generator that appends .md to a slash-terminated path produces
/product/uptime-monitoring/.md, which 404s on most hosts. Pick one convention per file and
keep it for every entry; the inconsistency is what breaks parsers that deduplicate or rewrite.
Duplicates, Alias Hosts and Migrated Origins
Counted per file: Stripe lists 27 URLs more than once, one quickstart three times
(/payments/quickstart.md) — what happens when a page belongs to several sections and is
copied into each. Checkly has 7 duplicates, Resend 2, Vite 1. Duplicates are legal Markdown, harmless to
a set-building parser, and they spend tokens to say nothing new.
Two files show what a host change does to an index. docs.anthropic.com/llms.txt and
platform.claude.com/llms.txt are byte-identical at 86,059 bytes with 784 links, and all 784
point at platform.claude.com — so the docs host is a pure alias. bun.sh/llms.txt
is the other direction: all 322 links point at bun.com, one of them at
mintlify.com. Both files are internally consistent, which is the property that matters. The
broken version of this pattern is a half-migrated file where some entries still name the retired host and
the redirect that used to cover them was dropped.
The Link Check Before You Ship
This is not the delivery check — that is a separate question of content type and caching. This one is about the URLs inside the file, and it takes about a minute:
# 1. every URL in the file, one per line
curl -s https://example.com/llms.txt | grep -oE '\]\([^)]+\)' | tr -d '])('
# 2. count the URL shapes — absolutes, root-relative, .md, trailing slash
curl -s https://example.com/llms.txt | grep -oE '\]\([^)]+\)' \
| awk '{ if ($0 ~ /^\]\(https?:/) a++; if ($0 ~ /^\]\(\//) r++; if ($0 ~ /\.md\)/) m++; if ($0 ~ /\/\)/) s++ } \
END { print "abs", a+0, "root", r+0, "md", m+0, "slash", s+0 }'
# 3. status of every unique link (follow redirects)
curl -s https://example.com/llms.txt | grep -oE 'https?://[^)]+' | sort -u \
| xargs -n1 -P8 curl -sIL -o /dev/null -w "%{http_code} %{url_effective}\n"
# 4. duplicates
curl -s https://example.com/llms.txt | grep -oE 'https?://[^)]+' | sort | uniq -d
# 5. does a twin return Markdown, or an HTML error page with a 404? Step 3 is the one that finds real breakage, and step 5 is the one that explains it. A 404 that returns HTML is a routing miss; a 200 that returns HTML is a site that never generated twins and a file that was written as if it had. If your index is hand-maintained, keep it in a form you can regenerate from your route list — the llms.txt generator emits absolute URLs by default for exactly this reason — then run the checker and confirm the link count it reports matches the number of pages you meant to list.
None of this ranks anything on its own. It decides whether the file you wrote is the file an agent reads, which is the only thing link hygiene was ever for.