llms.txt for Versioned Documentation
Documentation is the one site type where llms.txt already works — and the one where the version
prefix undoes it. We fetched /llms.txt from 15 documentation hosts on October 21, 2026,
then probed 9 version-pinned paths such as /v17/llms.txt and
/docs/12.x/llms.txt. Eight hosts serve a real file at the root. Of the nine
version-scoped paths, exactly one returns one.
What 15 Documentation Hosts Serve at the Root
Requested with a normal desktop user agent, redirects followed, bodies parsed for Markdown links. Eight hosts answered with a plain-text or Markdown body, seven did not — and the seven failures are not all 404s, which matters for the check further down.
| Host | Status | Type | Bytes | Links |
|---|---|---|---|---|
| docs.aws.amazon.com | 200 | text/plain | 418,290 | 2,247 |
| docs.nestjs.com | 200 | text/plain | 28,440 | 149 |
| nodejs.org | 200 | text/plain | 15,010 | 70 |
| react.dev | 200 | text/plain | 14,571 | 180 |
| vuejs.org | 200 | text/plain | 7,415 | 0 absolute |
| angular.dev | 200 | text/plain | 6,745 | 88 |
| docs.docker.com | 200 | text/markdown | 5,654 | 37 |
| svelte.dev | 200 | text/plain | 1,676 | 7 |
| fastapi.tiangolo.com | 404 | text/html | 91,075 | — |
| tailwindcss.com | 404 | text/html | 133,679 | — |
| kubernetes.io | 404 | text/html | 32,890 | — |
| go.dev | 404 | text/html | 26,974 | — |
| laravel.com | 404 | text/html | 86,133 | — |
| docs.python.org | 404 | text/html | 146 | — |
| docs.djangoproject.com | 200 | text/html | 54,135 | — |
The last row is the interesting one. docs.djangoproject.com/llms.txt answers 200 and hands
back 54,135 bytes of HTML — the version landing page at /en/6.1/. A checker that only reads
the status code calls that a pass. It is not a file at all. Python's 146-byte 404 and Kubernetes' 32,890-byte
404 are the honest version of the same answer.
The Spec Rule That Makes Per-Version Files Legal
llms.txt has no version field. What it has is path scoping: a file covers the URLs beneath its own
path, and when several files apply, the most specific one wins. That single rule is what makes
/docs/12.x/llms.txt a legitimate address — it describes one version's subtree, and the root
file describes everything else. The same mechanic is what makes per-language files work on
multilingual sites.
The catch is discovery. Nothing probes /docs/12.x/llms.txt unless the root file points at it,
and the only readers who will ever see a version file are the ones you tell about it. That asymmetry
decides the split: the root file is your front door, and a version file is a room.
What 9 Version-Pinned Paths Actually Return
We requested a version-scoped path from each host that has versioned docs. One returned a real file. Two returned 200 with HTML. Six returned 404 — one of them only after a redirect had appended a trailing slash, and one after the path moved to a different prefix entirely.
| Path probed | Status | Result |
|---|---|---|
| docs.aws.amazon.com/cdk/v2/guide/llms.txt | 200 | text/plain, 20,840 bytes |
| angular.dev/v17/llms.txt | 200 | text/html, 102,737 bytes |
| docs.nestjs.com/v10/llms.txt | 200 | text/html, 15,148 bytes |
| docs.docker.com/engine/llms.txt | 404 | text/html, 800,169 bytes |
| laravel.com/docs/12.x/llms.txt | 404 | redirect to /framework/docs/12.x/, HTML |
| docs.djangoproject.com/en/5.2/llms.txt | 404 | after a trailing-slash redirect |
| docs.python.org/3/llms.txt | 404 | text/html, 9,548 bytes |
| nodejs.org/docs/v20.x/llms.txt | 404 | text/plain, 14 bytes |
| nodejs.org/docs/latest/api/llms.txt | 404 | text/plain, 14 bytes |
Node's 14-byte body is the cleanest failure in the set: an empty text/plain response is unambiguous, costs nothing, and tells a parser to stop. Docker's 800,169-byte HTML 404 is the opposite — roughly 141 times the size of its real 5,654-byte root file, spent to say “no”.
Four Patterns in Production
Unversioned root only — React, Docker and Angular each serve one file with no version segment anywhere in it. React's 14,571 bytes contain zero version mentions and 180 links; the site simply does not expose older majors. Cheapest to maintain, and the file never breaks on release day.
The latest alias — Node lists all 70 links under /docs/latest/api/. The
alias moves with each release, so the index is written once and stays correct for years. What it does not
give an agent is any way to reach v20 docs, which is consistent with the docs site retiring old majors.
Version inside the path, file scoped to it — the AWS CDK v2 guide is the only probe that
returned a real file, 20,840 bytes of text/plain. AWS can afford this because its generator already emits
a per-service .md twin and a per-service llms.txt; the version is just another
path segment. The cost shows up in the root file: 2,247 links and 418,290 bytes in a single section, which
is past any reasonable token budget — see
size limits.
Tiers instead of versions — Svelte's 1,676-byte index does not list pages at all. It
lists llms-small.txt (52,700 bytes), llms-medium.txt, llms-full.txt,
plus four package-scoped subpath files, one of which is 481,379 bytes. Svelte never had to solve the
version problem because there is only one current version, and the split is by depth rather than by
release.
The Rules That Keep a Versioned Index Correct
- State the version inside the file. None of the eight live files we parsed says which release it documents. One line in the summary — “covers 12.x; older versions in the Optional section” — costs 12 tokens and prevents an agent from guessing.
- Point at canonicals, not pinned majors. A link to
/docs/latest/api/buffer.mdsurvives the next release;/docs/v22/api/buffer.mdhas to be edited on release day. - Delete end-of-life sections. Leaving a retired version in the index is how an agent ends up quoting a deprecated API. Keep the migration guide, drop the rest.
- One H1. Angular's file opens with
# Angularand then promotes its CLI section to a second H1 ninety-nine lines later, which turns one document into two as far as a parser is concerned. A version file has one more reason to get this right: its H1 is where the version belongs. - Link the file you actually ship. A version file nobody links to is a file nobody fetches — the root index has to name it.
The Five-Command Version Check
This runs in about a minute and catches the failure the status code hides. It is a narrower version of the general delivery check, aimed at the paths a release changes.
# 1. every supported major — status AND content type
for v in 10.x 11.x 12.x; do
curl -sS -o /dev/null -w "$v %{http_code} %{content_type} %{size_download}\n" \
"https://example.com/docs/$v/llms.txt"
done
# 2. the root file, first 8 lines — does it name its version?
curl -s https://example.com/llms.txt | head -8
# 3. what the links pin: latest alias or a fixed major?
curl -s https://example.com/llms.txt | grep -oE 'https?://[^)]+' \
| grep -oE '/(v|docs/)[0-9]+[^/]*/' | sort | uniq -c | sort -rn
# 4. the 200-with-HTML trap: a catch-all answers the path with the wrong body
curl -sI https://example.com/docs/12.x/llms.txt | grep -iE '^(HTTP|content-type)'
# 5. same probe through the docs host, not just the marketing host
curl -sS -o /dev/null -w "%{http_code} %{content_type}\n" \
https://docs.example.com/docs/12.x/llms.txt Command 1 is the one to run on release day, and command 4 is the one that explains a green dashboard with a broken file: Angular's v17 path and Nest's v10 path both answer 200, and both answer in HTML. Run the checker against the body it returns and you get a structural failure rather than a status failure — which is the truth. Generating the files from the same route list the docs build already has, as the generator does from a sitemap, is what keeps the per-version set from drifting apart after two or three releases.
Versioned documentation is the strongest case llms.txt has: the audience is technical, the pages are already Markdown, and the wrong version is actively harmful. That is exactly why the API docs pattern and a version check belong together — the structure is easy, and the routing is where it dies.