---
title: "llms.txt on Astro, Docusaurus, Framer and Ghost (and More)"
slug: llms-txt-setup-every-platform
published: 2026-10-24T03:08:29.671911+00:00
updated: 2026-10-24T03:08:29.671911+00:00
author: "M Asif Rahman"
author_url: https://ar.bd
category: "AI Readiness"
tags: check:C2, llms.txt, AI readiness, content, AI crawlers, platform:astro, platform:docusaurus, platform:framer, platform:ghost
description: "Publish a valid llms.txt on Astro, Docusaurus, Framer and Ghost: exact paths, plan gates, the baseUrl and redirect traps, and the curl check that proves it."
url: https://aiscan.site/blog/llms-txt-setup-every-platform
---

**Updated on 24 October 2026.** Sections for Astro, Docusaurus, Framer and Ghost were checked on the dates shown inside each one. The other seven platforms are being folded into this guide and keep their own posts until they arrive.

A valid `llms.txt` is one file at `/llms.txt` that returns HTTP 200 and contains an H1, at least one `##` section and Markdown links. That is the whole of AIScan's **C2** check. What changes from platform to platform is not the file but where you are allowed to put it: a folder, a code endpoint, a paid upload panel or a template route. This guide holds the platform-specific steps in one place, so you can jump to your stack.

## TL;DR

- **The pass bar is the same everywhere.** C2 wants a 200, one H1, one `##` section and Markdown links. It does not check the content type.
- **Astro:** drop `public/llms.txt`, or build `src/pages/llms.txt.ts` from your content collections. Starlight sites can use the `starlight-llms-txt` plugin.
- **Docusaurus:** `static/llms.txt` is served under your `baseUrl`. On a GitHub Pages project site that means `/project/llms.txt`, and the origin root returns 404.
- **Framer:** an upload under Site Settings, Hosting, Files, on the Pro or Enterprise plan only. Use a `.txt` extension and the root path.
- **Ghost:** no setting and no file root. Add a `routes.yaml` template route with `content_type: text/plain`, and judge success by the final URL, not the status code.
- **Google does not use the file.** Publish it for the agents and tools that fetch it. Whether any do is a separate question, measured in [does llms.txt actually work in 2026](https://aiscan.site/blog/does-llms-txt-actually-work-2026).

## Contents

| Platform | Where the file comes from | Section |
|---|---|---|
| Astro | static file, build-time endpoint, or Starlight plugin | [Astro](#astro) |
| Docusaurus | `static/` folder, a `postBuild` hook, or an npm plugin | [Docusaurus](#docusaurus) |
| Framer | a Pro-plan file upload | [Framer](#framer) |
| Ghost | a `routes.yaml` template route | [Ghost](#ghost) |
| Hugo | still its own guide: [llms.txt on Hugo](https://aiscan.site/blog/llms-txt-hugo) | coming |
| Next.js | still its own guide: [llms.txt on Next.js](https://aiscan.site/blog/llms-txt-nextjs) | coming |
| Replit | still its own guide: [llms.txt on Replit](https://aiscan.site/blog/llms-txt-replit) | coming |
| Shopify | still its own guide: [llms.txt on Shopify](https://aiscan.site/blog/llms-txt-shopify) | coming |
| Squarespace | still its own guide: [llms.txt on Squarespace](https://aiscan.site/blog/llms-txt-squarespace) | coming |
| Webflow | still its own guide: [llms.txt on Webflow](https://aiscan.site/blog/llms-txt-webflow) | coming |
| Wix | still its own guide: [llms.txt on Wix](https://aiscan.site/blog/llms-txt-wix) | coming |

## What every platform's file has to contain

The [llms.txt spec](https://llmstxt.org/) was proposed by Jeremy Howard of Answer.AI on 3 September 2024. An H1 carrying the project name is the only required section. Version 2, published 10 August 2026, added `rel="alternate"` and `rel="describedby"` link relations for discovery. No standards body owns it, and Google's AI optimization guide says Google Search ignores these files. The page was re-fetched on 24 October 2026 and still answers 200.

A minimum passing file:

```markdown
# Example

> Guides and API reference for Example.

## Docs

- [Quickstart](https://example.com/docs/quickstart): install and first request
- [API reference](https://example.com/docs/api): every endpoint, with examples
```

If you would rather not hand-roll it, the [llms.txt generator](https://aiscan.site/llms-txt-generator) drafts one from a URL. The mistakes that break a file from the inside are catalogued in [llms.txt validator: the common mistakes](https://aiscan.site/blog/llms-txt-validator-common-mistakes), and how it differs from your other root files is in [llms.txt vs robots.txt vs sitemap](https://aiscan.site/blog/llms-txt-vs-robots-txt-vs-sitemap).

How the platforms differ, from the guides folded in so far:

| Platform | Cost of a valid file | Stays current on its own | Plan gate | Checked |
|---|---|---|---|---|
| Astro | 2 minutes (static) or 15 minutes (endpoint) | Yes, with the endpoint | None | 28 August 2026 |
| Docusaurus | 5 minutes (static), 20 minutes (hook) | Yes, with a hook or plugin | None | 4 September 2026 |
| Framer | about 10 minutes | No | Pro or Enterprise | 2 September 2026 |
| Ghost | about 15 minutes | Yes, the template reads your posts | None | 30 August 2026 |

## Astro

*Verified 28 August 2026. Plugin version re-checked 24 October 2026.*

A valid `llms.txt` on Astro is either a two-minute file drop or a twenty-line endpoint that rebuilds itself from your content collections every time you run `astro build`. Astro's own documentation site does not publish one (checked 28 August 2026), so there is no upstream example to copy.

| If you want to | Do this | Where it lives | Time |
|---|---|---|---|
| Ship something valid today | Hand-write the file as a static asset | `public/llms.txt` | 2 minutes |
| Keep it current without editing it | Generate it from `getCollection()` | `src/pages/llms.txt.ts` | 15 minutes |
| Cover a Starlight docs site | Install `starlight-llms-txt` | `astro.config.mjs` | 5 minutes |

The endpoint is the version that survives a real blog. A hand-written file is stale the day you publish your next post.

### Astro path A: a static file in public/

Astro's [project structure guide](https://docs.astro.build/en/basics/project-structure/) describes `public/` as the home for "non-code, unprocessed assets", and its own example tree puts `robots.txt` there. Everything in `public/` is copied to the build root untouched.

1. Create **`public/llms.txt`** at the top level of your project, beside `src/` and `astro.config.mjs`.
2. Paste the Markdown above and swap in real absolute URLs.
3. Run **`npx astro build`** and confirm the file appears at `dist/llms.txt`.
4. Deploy.

That is a correct answer for a marketing site with eight pages. On anything with a content collection behind it, you will forget to update it.

### Astro path B: an endpoint built from your content collections

Astro's [endpoints guide](https://docs.astro.build/en/guides/endpoints/) says a `.js` or `.ts` file in `src/pages` becomes a route with the extension stripped, so `src/pages/data.json.ts` builds `/data.json`. The same rule gives you `/llms.txt` from `src/pages/llms.txt.ts`. In a statically generated site, custom endpoints "are called at build time to produce static files", so this costs nothing at runtime.

Set `site` in **`astro.config.mjs`** first, because the spec wants absolute URLs:

```js
// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({ site: 'https://example.com' });
```

Then create the endpoint:

```ts
// src/pages/llms.txt.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';

export const GET: APIRoute = async ({ site }) => {
  const posts = (await getCollection('blog', ({ data }) => !data.draft))
    .sort((a, b) => +b.data.pubDate - +a.data.pubDate);

  const body = [
    '# Example',
    '',
    '> Guides, API reference and engineering notes.',
    '',
    '## Blog',
    '',
    ...posts.map(
      (p) => `- [${p.data.title}](${new URL(`/blog/${p.id}/`, site)}): ${p.data.description}`,
    ),
    '',
  ].join('\n');

  return new Response(body, {
    headers: { 'content-type': 'text/plain; charset=utf-8' },
  });
};
```

`getCollection()` takes an optional filter function, which is how drafts stay out. Your `src/content.config.ts` schema already guarantees that `title` and `description` exist on every entry, and that is what makes the generated file reliable rather than hopeful.

### Astro path C: Starlight documentation sites

If your docs run on Starlight, [`starlight-llms-txt`](https://delucis.github.io/starlight-llms-txt/) does the whole job. It is written by Chris Swithinbank, a Starlight maintainer. Version 0.11.0 was published on 1 July 2026, and npm listed 0.12.0 as the latest on 24 October 2026. Install it with **`npm i starlight-llms-txt`**, add `starlightLlmsTxt()` to the `plugins` array inside `starlight()` in `astro.config.mjs`, and set `site`. It generates `llms.txt`, `llms-full.txt` and `llms-small.txt`, and the plugin docs tell you to preview at `localhost:4321/llms.txt`.

### Three Astro traps

**A trailing slash returns 404.** Astro's endpoints guide is explicit: routes whose URLs include a file extension "can only be accessed without a trailing slash", whatever your `build.trailingSlash` setting says. So `/llms.txt/` fails while `/llms.txt` works. Always test the bare path.

**On-demand rendering regenerates the file on every request.** If you run an adapter with server output, add `export const prerender = true;` to the endpoint so it is written once at build time.

**Do not chase the content type.** C2 does not check it. Both `nextjs.org/docs/llms.txt` and `vercel.com/llms.txt` serve `text/plain; charset=utf-8`, and both pass.

## Docusaurus

*Verified 4 September 2026. Package versions re-checked 24 October 2026.*

A Docusaurus site can publish a completely valid `llms.txt` and still score zero on it. The reason is one config value. On GitHub Pages, the most common deployment target, a project site lives at `https://your-org.github.io/your-project/`, so `baseUrl` is `/your-project/` and the file you dropped into `static/` answers at `/your-project/llms.txt`. Nothing answers at the origin root. The build was fine. The URL was wrong.

| If you want to… | Do this | Time | Stays current on its own |
|---|---|---|---|
| Ship a file today | Put `llms.txt` in `static/` | 5 min | No, you edit it by hand |
| Generate it from your real routes | Add an inline plugin with a `postBuild` hook | 20 min | Yes, every build |
| Skip writing code | Install a published plugin from npm | 10 min | Yes, every build |
| Serve it at the origin root | Deploy at `baseUrl: '/'`, or add a host-level rewrite | Varies | N/A |

### Why the file lands at a URL agents will not ask for

Docusaurus copies `static/` straight into the build output. Its [Static Assets page](https://docusaurus.io/docs/static-assets) says: *"Every file you put into that directory will be copied into the root of the generated build folder with the directory hierarchy preserved."* The next paragraph is the one that matters: *"for site `baseUrl: '/subpath/'`, the image `/static/img/docusaurus.png` will be served at `/subpath/img/docusaurus.png`."* According to the [deployment guide](https://docusaurus.io/docs/deployment), *"For a site deployed at `https://my-org.com/my-project/`, `baseUrl` is `/my-project/`."*

| Deployment | `baseUrl` | `static/llms.txt` is served at | Origin root `/llms.txt` |
|---|---|---|---|
| `docs.example.com` (own domain) | `/` | `https://docs.example.com/llms.txt` | 200 |
| `example.com/docs/` (subpath) | `/docs/` | `https://example.com/docs/llms.txt` | 404 |
| `org.github.io/project/` (GitHub Pages project site) | `/project/` | `https://org.github.io/project/llms.txt` | 404 |

A subpath file is not invalid. The spec, fetched from [llmstxt.org](https://llmstxt.org/) on 4 September 2026, allows it: *"The llms.txt file spec is for files named llms.txt, at the root path /llms.txt of a website or at any subpath (e.g. /docs/llms.txt). A file covers the URLs under its path, and where more than one file applies, agents should use the most specific one."* So `/docs/llms.txt` is spec-legal and useful. It is also invisible to any scanner that probes only the origin root, ours included. Know which one you published before you go looking for the score.

### Docusaurus route 1: the static folder

1. Create `static/llms.txt` at the root of your Docusaurus project, beside `docusaurus.config.js`.
2. Write the file, listing pages that answer questions (see below).
3. Run **`npm run build`**, then **`npm run serve`**, and open `http://localhost:3000` plus your `baseUrl` and `llms.txt`.
4. Deploy.

If your team keeps generated assets somewhere other than `static/`, the `staticDirectories` option in `docusaurus.config.js` takes an array of paths, all copied to the build output as-is.

### Docusaurus route 2: a postBuild hook that reads your real routes

Hand-written indexes drift the moment somebody adds a page. According to the [lifecycle API reference](https://docusaurus.io/docs/api/plugin-methods/lifecycle-apis), `postBuild(props)` is *"Called when a (production) build finishes"* and receives `siteDir`, `outDir`, `baseUrl`, `siteConfig`, `routesPaths` and `routesBuildMetadata`, which carries a `noIndex` flag per location. Add this to `docusaurus.config.js` as an inline plugin:

```js
plugins: [
  function llmsTxtPlugin() {
    return {
      name: 'llms-txt',
      async postBuild({siteConfig, routesPaths, routesBuildMetadata, outDir}) {
        const fs = await import('node:fs/promises');
        const path = await import('node:path');
        const lines = [
          `# ${siteConfig.title}`,
          '',
          `> ${siteConfig.tagline}`,
          '',
          '## Docs',
        ];
        for (const route of routesPaths) {
          if (routesBuildMetadata?.[route]?.noIndex) continue;
          if (route === '/404.html') continue;
          lines.push(`- [${route}](${siteConfig.url}${route})`);
        }
        await fs.writeFile(
          path.join(outDir, 'llms.txt'),
          lines.join('\n') + '\n',
        );
      },
    };
  },
],
```

Writing into `outDir` puts the file at the build root, the same place `static/` lands, so the `baseUrl` rule above still applies. Before you ship it, replace the raw route path with a real title, and group routes under separate `##` headings rather than one flat list. Agents read the headings.

### Docusaurus route 3: an npm plugin

| Package | Version | What it produces |
|---|---|---|
| [`docusaurus-plugin-llms`](https://www.npmjs.com/package/docusaurus-plugin-llms) | 0.6.0 on 4 September 2026; 0.6.1 on 24 October 2026 | An llms.txt index following the llmstxt.org format |
| [`@signalwire/docusaurus-plugin-llms-txt`](https://www.npmjs.com/package/@signalwire/docusaurus-plugin-llms-txt) | 1.2.2 on both dates | Markdown twins of your HTML pages, plus an llms.txt index |

The second solves a problem the first does not touch: your links can point at Markdown rather than HTML, which is what an agent wants at the other end. Both are community packages, not part of `@docusaurus/core`. Read the README and pin the version.

### Choosing which Docusaurus pages to list

A docs site's route list includes tag pages, archive pages and pagination, and none of those help a model. Start with the H1 naming the project, follow it with a blockquote summary, group links under `##` headings such as `## Getting started` and `## API reference`, give every link a short note, and leave out anything carrying `noIndex`.

### Confirm which Docusaurus URL you shipped

```bash
curl -sI https://yoursite.com/llms.txt | head -1
curl -sI https://yoursite.com/your-baseurl/llms.txt | head -1
```

Expect `HTTP/2 200` from whichever one matches your `baseUrl`. A 404 from the origin root while the subpath returns 200 is the failure this section exists for. Status codes can be trusted on Docusaurus: verified on 4 September 2026, `docusaurus.io/llms.txt` returns a real **404** with a `text/html` body of 20,038 bytes, and an invented path returns the identical 404. Then read the body with `curl -s https://yoursite.com/llms.txt | head -20`. The first non-blank line must be a single `#` heading. If you see HTML, you are looking at your 404 page through a redirect.

AIScan's C2 check probes `/llms.txt` at the origin of the URL you give it. Give it the subpath URL and it still asks the origin root, so a spec-legal `/docs/llms.txt` reads as absent. That is a limit of the check rather than a fault in your file.

## Framer

*Verified 2 September 2026. Framer's help articles are script-rendered and were not re-fetched on 24 October 2026, so the plan and path details below carry their September date.*

On Framer, `llms.txt` is not a file you commit and not a text box you fill in. It is an upload in a hosting panel, and that panel is switched off below a certain price.

| Question | Answer |
|---|---|
| Where does the file live? | Uploaded under **Site Settings → Hosting → Files**, with the path set to the domain root |
| Which plans can do it? | **Pro and Enterprise only.** Free and Basic sites cannot host a static file at all |
| What formats are accepted for llms.txt? | **TXT** for a standard file, **JSON** for structured data |
| Does it work before publishing? | No. The file appears only after the site is published |
| Does Framer generate it for you? | No. Framer auto-generates `robots.txt` and `sitemap.xml`, not `llms.txt` |
| Time to fix | About ten minutes, once you are on a plan that allows it |

### The Framer plan gate comes first

Framer's static files article says, in its own words, "This feature is available on Pro and Enterprise plans." There is no workaround.

| Source | What it says about plans | Last updated |
|---|---|---|
| Framer help, "Static files" | "available on Pro and Enterprise plans" | 10 August 2026 |
| Framer help, "Add an llms.txt file to your site" | Pro and Enterprise in the body, and again in its FAQ | 10 August 2026 |
| Framer pricing, snapshot in Framer's own `llms.txt` | Pro is $30 per month, billed monthly | 30 August 2026 |

According to the same article, "These settings used to live in the Domains dashboard and are now in your project's Site Settings, under Hosting." Older tutorials still point at the Domains dashboard, so ignore them.

### Upload the file in Site Settings

1. Open your **dashboard**.
2. Open **Site Settings**.
3. Go to **Hosting → Files**.
4. Click the **Add** button.
5. Upload your file.
6. **Set the file path.** Framer's step says "do not include the file name", and its worked example is a path of `/pdfs` producing `example.com/pdfs/example.pdf`. Its FAQ answers the root case directly: "You can upload a file and assign it to a root-level path such as `/ads.txt` or `/llms.txt`." So upload a file named `llms.txt` and give it the root path.
7. **Publish** the site.

Delivery depends on the MIME type. `text/plain`, `text/xml`, `application/json`, `application/pdf` and the image, audio and video families render in a browser, and in Framer's phrasing "all other file types will automatically download instead of rendering." Upload Markdown with an `.md` extension and a crawler gets a download prompt rather than a body. Use `.txt`. The same panel handles `.well-known` files: set the path to `/.well-known` for `security.txt`, `assetlinks.json` or an AI plugin manifest.

### Point the Framer links at Markdown twins

Framer serves a Markdown version of every optimised page at the same URL, through an `Accept: text/markdown` header or by appending `?md`. Measured on 2 September 2026, `www.framer.com/pricing` with that header returned **200**, `text/markdown; charset=utf-8`, 11,651 bytes, opening with YAML front matter. Framer's own file, fetched from `www.framer.com/llms.txt` that day, was 5,900 bytes and points at `https://www.framer.com/ai/?md` and its siblings rather than the HTML pages. It still answered 200 on 24 October 2026.

The caveat comes from Framer's AI agents article: "Markdown is only generated for optimized pages." That is not theoretical. Framer's own help articles are not optimised, and `Accept: text/markdown` on `/help/articles/static-files/` returns `text/html`. Test any URL before you put its `?md` form in your index.

### Verify the Framer file landed

| What you see | What it means |
|---|---|
| `200` and `text/plain` | Working. Framer serves it from the edge with `server: Framer` |
| `404`, tiny body | The upload is missing, the path is wrong, or the site has not been published since |
| A download prompt in the browser | Wrong extension. Re-upload as `.txt` |
| `200` but empty content | The file uploaded is empty; C2 will still fail |

Framer returns a real 404 for a missing root file. Verified on 2 September 2026, `www.framer.com/llms-full.txt` returned **404** with a ten-byte body, while an unknown page path returned the site's own 404 page. Framer does not answer 200 with the homepage the way [Ghost](#ghost) does, so a status check on Framer is trustworthy.

## Ghost

*Verified 30 August 2026. The routing page was re-fetched on 24 October 2026 and still documents `content_type`.*

Ghost has no setting for llms.txt and no folder you can drop a file into that lands on the site root. The fix takes about fifteen minutes: one route in `routes.yaml`, one Handlebars template, one upload. Ghost's routing documentation says routes render HTML unless you override it "by specifying a `content_type` property with a custom mime-type". That property is the whole trick.

| If you are on… | Do this | Where the file ends up | Time |
|---|---|---|---|
| Ghost(Pro) | Add a template route in `routes.yaml`, upload it under **Settings > Labs** | `/llms.txt/` (with the slash) | ~15 min |
| Self-hosted Ghost | Same route, or an alias in nginx or Caddy | `/llms.txt/`, or `/llms.txt` via the proxy | ~10 min |
| Any Ghost site | Check the final URL and content type, not the status code | n/a | 1 min |

### Why Ghost gives you nowhere to put the file

Ghost serves theme files under `/assets/` and uploaded media under `/content/images/`. Neither is the site root, and Ghost admin has no file manager. So on Ghost the file cannot be a file. It has to be a route that renders text, from your posts, every time an agent asks.

### Add the Ghost route

Ghost keeps its routing config at `content/settings/routes.yaml`:

```yaml
routes:
  /llms.txt/:
    template: llms-txt
    content_type: text/plain

collections:
  /:
    permalink: /{slug}/
    template: index

taxonomies:
  tag: /tag/{slug}/
  author: /author/{slug}/
```

Keep the `collections` and `taxonomies` blocks. Uploading a routes file that omits them replaces your whole URL structure. **The trailing slash is not optional.** According to the routing page, "Trailing slashes are required" for dynamic routing, and Ghost "automatically forces trailing slashes". Writing the route as `/llms.txt/` is what makes it resolve.

### Write the Ghost template

Create `llms-txt.hbs` in your theme root, alongside `index.hbs`. The `{{#get}}` helper queries the Content API server-side before the template renders:

```handlebars
# {{@site.title}}
> {{@site.description}}

## Posts
{{#get "posts" limit="all" order="published_at desc"}}{{#foreach posts}}- [{{title}}]({{url absolute="true"}}): {{excerpt words="20"}}
{{/foreach}}{{/get}}

## Start here
- [About]({{@site.url}}/about/): who publishes this
```

The `{{#get}}` helper reads posts, tags, authors, tiers and newsletters. Pages are not one of those resources, so list your important pages by hand in a second `##` section, as above. Zip the theme and upload it in Ghost admin. The uploader runs GScan, and according to Ghost's theme docs "any fatal errors will prevent the theme from being used", so a broken Handlebars block is caught before it reaches the site. Then upload `routes.yaml` under **Settings > Labs**. Editing the file on a self-hosted box works too, but Ghost has to be restarted.

### Self-hosted Ghost shortcut

Behind nginx or Caddy you can skip the template and serve a static file at the bare path, which `routes.yaml` cannot give you:

```nginx
location = /llms.txt {
    alias /var/www/llms.txt;
    default_type text/plain;
}
```

The trade-off: the proxy version never updates itself, while the template version regenerates from your posts on every request.

### Read the Ghost response, not the status code

Verified on 30 August 2026, we requested `/llms.txt` from two Ghost publications that do not publish one, [Platformer](https://www.platformer.news) and [404 Media](https://www.404media.co). Both returned **302** to the homepage, which then returned **200** with `text/html`. A follow-redirects status check therefore reports 200 on a Ghost site with no llms.txt at all. Check the destination:

```bash
curl -sIL https://example.com/llms.txt | grep -iE '^(HTTP|location|content-type)'
```

You want the chain to end on your own `/llms.txt/` with `content-type: text/plain`. If the final `location` is your homepage, the route is not live.

| What you saw | What it means | Fix |
|---|---|---|
| 302 to the homepage | The route never loaded | Re-upload `routes.yaml`; confirm the key reads `/llms.txt/` with both slashes |
| Renders, but `text/html` | `content_type` missing or misindented | YAML nesting is two spaces, and Ghost is strict about it |
| Renders as text, nothing under `## Posts` | Wrong template filename | `routes.yaml` drops the `.hbs`, so `template: llms-txt` needs `llms-txt.hbs` |
| 200 with content, C2 still fails | A Ghost page slug collides with the route | Routes and slugs know nothing about each other, so one wins. Rename the page |

Re-run the scan after your next theme change, because a theme upload replaces `llms-txt.hbs`.

## Confirm any platform's file passes C2

Scan first. **`npx aiscan-cli yoursite.com`** returns the whole [content dimension](https://aiscan.site/docs/checks/content), and row **C2** names which of the four conditions failed rather than telling you something is wrong. It is free and needs no account. To check by hand:

```bash
curl -sSL -o llms.txt -w '%{http_code}\n' https://example.com/llms.txt  # expect 200
head -1 llms.txt                                                    # expect "# Example"
grep -c '^## ' llms.txt                                             # expect >= 1
grep -cF '](http' llms.txt                                          # expect >= 1
```

| C2 can tell you | C2 cannot tell you |
|---|---|
| The file exists and returns 200 | Whether the URLs inside it resolve |
| It has an H1 and a `##` section | Whether the descriptions are accurate |
| It carries Markdown links | Whether any assistant has ever fetched it |

Server logs answer the last one. Filter for requests to `/llms.txt` and see who turns up. Read C2 next to C1 (Markdown for agents) and C3 (structured HTML), because the same build step usually moves all three. Every other fix guide sits at [aiscan.site/guides](https://aiscan.site/guides).

