What is llms.txt?
llms.txt is a Markdown file served at /llms.txt that gives AI systems a curated, plain-text map of a website’s most important content. The format was proposed in late 2024 by Jeremy Howard at Answer.AI. For the full validation spec, see our llms.txt checker.
Where should llms.txt live in a Next.js app?
Two valid locations, depending on whether your content is static or generated. For most sites, the static path is correct: drop a file at public/llms.txt and Next.js serves it at the site root with the correct text/plain Content-Type. For sites where the page list is generated — from a CMS, a database, or a build-time index — use a route handler at src/app/llms.txt/route.ts. The five steps below cover the static path; the dynamic fork is described after.
Step 1 — Create the public/llms.txt file
From your project root, create the file under public/. Next.js auto-serves anything in this folder at the site root, so the file becomes available at /llms.txt the moment it’s on disk — no route, no build step, no configuration change.
# from your Next.js project root
touch public/llms.txtStep 2 — Add the required Markdown structure
The llmstxt.org spec mandates two elements: an # H1 with your site name, and a blockquote summary one or two sentences long. After that, add ## H2 section headings to group your links. Sections are optional but recommended for any site with more than a handful of pages.
Step 3 — List your most authoritative pages
Under each section heading, add Markdown links in the format - [Title](URL): description. Curate to your most valuable pages — documentation, API reference, policies, key product pages. Quality over quantity: AI systems prefer a short, high-signal index over a full sitemap. Aim for 10–50 links.
# Acme Cloud Widgets
> Widgets-as-a-service for serverless apps. Deploy in one command.
## Documentation
- [Getting started](https://acme.dev/docs/start): Zero to deployed in 60 seconds
- [API reference](https://acme.dev/docs/api): REST endpoints and authentication
- [SDKs](https://acme.dev/docs/sdks): Official client libraries
## Policies
- [Privacy policy](https://acme.dev/privacy): Data handling and retention
- [Terms of service](https://acme.dev/terms): Usage terms
## Optional
- [Sitemap](https://acme.dev/sitemap.xml)
- [llms-full.txt](https://acme.dev/llms-full.txt): Full content bundle
An optional llms-full.txt companion file concatenates the full content of those URLs into one Markdown bundle — useful for docs-heavy sites where the index alone doesn’t carry enough context.
Step 4 — Verify it serves correctly
Deploy or start your dev server, then send a HEAD request to confirm the file returns HTTP 200 with a text/plain Content-Type. Next.js sets this header automatically for .txt files under public/; no configuration is needed.
curl -I https://yoursite.com/llms.txt
# Expected response:
# HTTP/2 200
# content-type: text/plain; charset=utf-8
Step 5 — Validate against the llmstxt.org spec
Run your URL through the llms.txt checker to confirm the file passes the 9-check compliance suite — H1 present, blockquote summary, valid Markdown, correct link format, accessible URLs, proper Content-Type, and the optional llms-full.txt companion. Failed checks come with a one-line fix each.
How do I generate llms.txt dynamically?
When your page list comes from a database, CMS, or build-time index, use a Next.js route handler at src/app/llms.txt/route.ts. Return a Response with an explicit Content-Type: text/plain header and the Markdown body assembled from your data source. File-system routes win when both exist, so delete public/llms.txt if you switch to the handler.
// src/app/llms.txt/route.ts
import { getPublishedPages } from "@/lib/content";
export async function GET() {
const pages = await getPublishedPages();
const body = [
"# Acme Cloud Widgets",
"",
"> Widgets-as-a-service for serverless apps.",
"",
"## Documentation",
...pages.map((p) => `- [${p.title}](${p.url}): ${p.description}`),
].join("\n");
return new Response(body, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "public, max-age=3600",
},
});
}
The route handler example targets Next.js 13+ (App Router). The static-file path works on the Pages Router with the same public/llms.txt location.
Common pitfalls when adding llms.txt
- Pages behind authentication. Links in llms.txt must resolve without a session. Public docs, policies, and marketing pages only.
- Wrong Content-Type. If your CDN or middleware rewrites
.txttotext/html, many validators reject the response. - JS-only navigation targets. AI crawlers fetch URLs without running JavaScript; if a linked page’s primary content requires a hydrated app, the link is effectively dead.
- Missing blockquote. The
>summary line directly under the H1 is mandatory; without it, strict validators fail the file. - Stale links. Linked URLs that 404 or redirect off-domain hurt your citation quality. Re-check after every site restructure.
Frequently asked questions
- Does llms.txt need to live at the site root?
- The canonical path is /llms.txt at the root of your domain. Some validators also accept /docs/llms.txt or /.well-known/llms.txt, but the root path is what most AI clients try first. Publish at the root; add alternate paths only if you've already shipped them.
- Can I generate llms.txt dynamically with Next.js?
- Yes. Create a route handler at src/app/llms.txt/route.ts that returns a Response with Content-Type: text/plain. Build the Markdown from your CMS, database, or sitemap source. Note: file-system routes (public/llms.txt) win when both exist — delete the static file if you want the route handler to take over.
- Do I need an llms-full.txt file too?
- Not required, but it helps for docs-heavy sites. llms-full.txt is the optional companion that concatenates the full text of every page in your llms.txt into a single Markdown file. AI clients fetch it when they want more context than the index alone provides. Same Content-Type and location rules apply.
- Does my llms.txt need a special Content-Type header?
- It should be served with Content-Type: text/plain (or text/markdown). Next.js sets text/plain automatically for .txt files under public/. If you generate it dynamically, set the header explicitly in your Response. Avoid text/html — most validators treat that as a misconfiguration.
- How do I keep my llms.txt up to date as my site grows?
- For static files, edit and redeploy. For dynamic generation, the route handler regenerates on every request (or on a configurable cache window via Cache-Control). For high-velocity sites, hook the regeneration into your sitemap or content-publish flow so llms.txt stays in sync without manual touch.