/ llmtxt.info

llms.txt with Astro

Two approaches: drop a file in public/ for instant deployment, or generate it dynamically from your content collections with a TypeScript endpoint.

Last updated:

Approach 1, Static file in public/

The simplest option. Create a plain text file at public/llms.txt in your project root. Astro copies the entire public/ directory verbatim to dist/ during astro build, so the file is served at /llms.txt with zero configuration.

static approach, public/llms.txt
# Place the file at: public/llms.txt
# Astro copies everything in public/ to the output directory as-is.
# Result: served at https://yoursite.com/llms.txt, no code changes needed.

# After deploy, verify:
curl -I https://yoursite.com/llms.txt
# Expected: HTTP/2 200 | Content-Type: text/plain

When to use this: your content doesn’t change often, you want zero build-time overhead, or you maintain the file manually. Works identically on Cloudflare Pages, Vercel, Netlify, and any other Astro host.

Approach 2, TypeScript endpoint with getCollection()

Astro matches the double extension .txt.ts and treats src/pages/llms.txt.ts as an API endpoint that responds at /llms.txt. In SSG mode (the default), Astro pre-renders it to a static dist/llms.txt at build time, no runtime cost.

src/pages/llms.txt.ts
// src/pages/llms.txt.ts
// Astro treats src/pages/foo.ext.ts as a route for /foo.ext
// In SSG mode (default), it pre-renders to dist/llms.txt at build time.

import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';

export const GET: APIRoute = async () => {
  // Replace 'docs' with your actual content collection name
  const docs = await getCollection('docs');

  const lines = [
    '# My Site',
    '',
    '> One-sentence description of what this site is about.',
    '',
    '## Documentation',
    '',
    ...docs
      .filter((d) => !d.data.draft)
      .sort((a, b) => (a.data.order ?? 999) - (b.data.order ?? 999))
      .map((d) => `- [${d.data.title}](https://yoursite.com/${d.slug}/): ${d.data.summary ?? ''}`.trimEnd()),
    '',
    '## Optional',
    '',
    '- [Changelog](https://yoursite.com/changelog/): version history.',
  ];

  return new Response(lines.join('\n'), {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
};

Key points:

  • Replace 'docs' with your actual collection name from src/content/config.ts.
  • Filter out drafts before mapping: !d.data.draft.
  • Each line annotation (: short note) is optional but strongly recommended , it gives LLMs context about each page without them having to fetch it.
  • Always set Content-Type: text/plain; charset=utf-8 in the response.

Adding llms-full.txt

The llms-full.txt companion file inlines the full body of every page, useful for RAG pipelines and LLM clients that want the entire corpus at once. Create a parallel endpoint:

src/pages/llms-full.txt.ts
// src/pages/llms-full.txt.ts
// Generates the full-content companion file.
// Each entry gets its full Markdown body inlined, useful for RAG pipelines.

import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';

export const GET: APIRoute = async () => {
  const docs = await getCollection('docs');
  const sections: string[] = [];

  for (const doc of docs.filter((d) => !d.data.draft)) {
    sections.push(
      `# ${doc.data.title}`,
      '',
      `URL: https://yoursite.com/${doc.slug}/`,
      '',
      doc.body,   // raw Markdown, no HTML, ideal for LLMs (Astro 4+)
      '',
      '---',
      '',
    );
  }

  return new Response(sections.join('\n'), {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
};

doc.body contains raw Markdown (available since Astro 4), which is ideal for LLMs: no HTML tags, clean semantic text. For Astro 3, use the render() helper and strip tags with a simple regex.

Verify after build

verify
# Build the project
npx astro build

# Check static output (works for both approaches in SSG mode)
cat dist/llms.txt

# Or start preview server and curl:
npx astro preview &
curl http://localhost:4321/llms.txt
curl http://localhost:4321/llms-full.txt

After deploying, paste your live URL into the validator to confirm spec compliance: exactly one H1, valid link syntax (- [title](https://...)), all URLs absolute, no empty sections.

Which approach to choose

  • Static public/llms.txt, best for sites updated infrequently. Zero overhead, no TypeScript required, works everywhere.
  • TypeScript endpoint src/pages/llms.txt.ts, best for documentation sites with many auto-generated pages. The file stays in sync automatically on every build without manual edits.

Full creation guide · Next.js guide · WordPress guide

Sources