---
title: "How to publish a valid llms.txt on Docusaurus"
slug: llms-txt-docusaurus
published: 2026-09-04T13:23:32.028359+00:00
updated: 2026-09-04T13:23:32.028359+00:00
author: "Asif Rahman"
author_url: https://masifrahman.com
category: "AI Readiness"
tags: check:C2, platform:docusaurus, llms.txt, Docusaurus, AI crawlers, static site generators, AI readiness
description: "On Docusaurus, baseUrl decides whether your llms.txt answers at the origin root. Three routes to a valid file, and the curl check that proves which one shipped."
url: https://aiscan.site/blog/llms-txt-docusaurus
---

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 platform's 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. Below are three routes to the file, the check that tells you which URL you actually shipped, and a fix that takes about ten minutes.

## Quick summary

| 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 |

The check this maps to is **C2**, in AIScan's `content` dimension. C2 asks for a file at `/llms.txt` and reads whether it parses.

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

Docusaurus copies `static/` straight into the build output. Its own [Static Assets page](https://docusaurus.io/docs/static-assets) says, verbatim: *"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 here: *"for site `baseUrl: '/subpath/'`, the image `/static/img/docusaurus.png` will be served at `/subpath/img/docusaurus.png`."*

That applies to every file in `static/`, llms.txt included. And according to Docusaurus's [deployment guide](https://docusaurus.io/docs/deployment), the value you need is unambiguous: *"For a site deployed at `https://my-org.com/my-project/`, `baseUrl` is `/my-project/`."* Docs sites hosted under a repository path inherit that prefix automatically.

| 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 llms.txt specification, fetched from [llmstxt.org](https://llmstxt.org/) on 4 September 2026, allows it in as many words: *"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.

## Route 1: the static folder

Fastest path, and the right one for a hand-curated index.

1. Create `static/llms.txt` at the root of your Docusaurus project, beside `docusaurus.config.js`.
2. Write the file (see the section on choosing pages 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.

## Route 2: a postBuild hook that reads your real routes

Hand-written indexes drift the moment somebody adds a page. Docusaurus hands you the finished route list at build time. According to its [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, which is the same place `static/` lands, so the `baseUrl` rule from the table above still applies. Two things worth doing 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.

## Route 3: an npm plugin someone already wrote

Two published packages do this, both fetched from the npm registry on 4 September 2026:

| Package | Version | Last published | What it produces |
|---|---|---|---|
| [`docusaurus-plugin-llms`](https://www.npmjs.com/package/docusaurus-plugin-llms) | 0.6.0 | 1 September 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 | 23 July 2025 | Markdown twins of your HTML pages, plus an llms.txt index |

The second one 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. Read the package README before installing either, and pin the version: both are community packages, not part of `@docusaurus/core`, which is at 3.10.2 as of this writing.

## Choosing which pages to list

Keep it to pages that answer questions. 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. The spec calls this the only required section.
- Follow it with a blockquote summary.
- Group links under `##` headings such as `## Getting started`, `## API reference`, `## Guides`.
- Give every link a short note after the URL saying what the page covers.
- Leave out anything carrying `noIndex`, which is why the hook above reads `routesBuildMetadata`.

## Confirm which URL you actually shipped

Start with the scan, because it reads the file rather than the status code:

```
npx aiscan-cli yoursite.com
```

Read the **C2** row. A pass means a file answered at `/llms.txt` and parsed. If you prefer to check by hand, ask both URLs and compare:

```
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 exact failure this guide exists for.

You can trust these status codes on Docusaurus, which is more than you can say for some platforms. 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. Docusaurus does not answer 200 for files that are absent, so a status check here means something.

Then read the body, not just the header:

```
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.

## Where AIScan fits, and where it doesn't

[AIScan](https://aiscan.site/)'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 limitation of the check rather than a fault in your file, and it is on the fix list. What the scan does tell you is whether the file parses, whether the H1 and blockquote are in the right order, and how C2 sits against the rest of the `content` dimension.

What no scanner can judge is whether the pages you listed are the pages a reader needs. That part is editorial. If you would rather edit something than face a blank file, the [llms.txt generator](https://aiscan.site/llms-txt-generator) produces a first draft in the right shape, ready to paste into `static/llms.txt`.

## Ship it, then re-scan

Deploy, wait for the build, and run `npx aiscan-cli yoursite.com` again. Watch the **C2** row in the `content` dimension, and check E1 while you are there, since a host that soft-404s makes every file probe on the report unreliable.

Neighbouring guides worth reading: [llms.txt on Hugo](https://aiscan.site/blog/llms-txt-hugo) covers the other static generator where `static/` is the zero-config route, [llms.txt on Astro](https://aiscan.site/blog/llms-txt-astro) shows the build-time endpoint pattern, and [the mistakes that break llms.txt files](https://aiscan.site/blog/llms-txt-validator-common-mistakes) catalogues what goes wrong inside the file itself. The full rubric for this dimension lives on [the content checks page](https://aiscan.site/docs/checks/content), and every platform guide we publish is indexed at [aiscan.site/guides](https://aiscan.site/guides).
