Table of contents
AIScan's C2 check is worth 6 points, and on Next.js you can earn it in about two minutes. C2 passes when /llms.txt returns 200 and the file contains an H1, at least one ## section, and markdown links. That is the whole bar. Next.js gives you two honest ways to serve it: a static file in public/, or a route handler that builds the file from your own content at build time. Both are below, with the exact paths, the static-export gotcha, and the command that proves it landed.
Quick summary
| If you want… | Do this | Exact file | Time |
|---|---|---|---|
| The fastest possible pass | Drop a static file | public/llms.txt | 2 min |
| A file that stays current | Prerender a route handler | app/llms.txt/route.ts | 15 min |
It to work under output: 'export' | Add export const dynamic = 'force-static' | app/llms.txt/route.ts | 1 extra line |
Pages Router (no app/ dir) | Generate into public/ from a prebuild script | scripts/gen-llms.mjs | 20 min |
| To skip writing it yourself | Use the llms.txt generator | n/a | 1 min |
Verify any of them with npx aiscan-cli yoursite.com and read the C2 row.
Why Next.js sites miss this one
There is no webroot to drop a file into. Everything is a route, and the App Router has file conventions for the neighbours (robots.ts, sitemap.ts, manifest.json, opengraph-image), but no llms.ts. So llms.txt falls into the gap between "static asset" and "generated metadata," and most teams never pick a lane. Next.js itself picked one: nextjs.org/docs/llms.txt returns 200 with a # Next.js Documentation H1 and a version note for the release it was built from.
Path 1: a static file in public/
Files inside public are referenced from the base URL, so public/llms.txt is served at /llms.txt.
- Create
public/llms.txtin your project root (the same level aspackage.json, not insideapp/). - Paste a skeleton and edit it:
# Acme Inc.
> One or two sentences on what Acme does and who it is for.
## Docs
- [Getting started](https://acme.com/docs/getting-started): install and first deploy
- [API reference](https://acme.com/docs/api): every endpoint with examples
## Company
- [Pricing](https://acme.com/pricing): plans and limits
- Commit and deploy. No config change, no rebuild trick.
Two things to know. Next.js applies Cache-Control: public, max-age=0 to everything in public, because it cannot safely cache assets that may change, so your edits go live on the next deploy without a purge. And the content type is your host's call: on Vercel, nextjs.org/docs/llms.txt and vercel.com/llms.txt both come back as text/plain; charset=utf-8, which passes C2 fine.
The cost of Path 1 is that it rots. Ship a page, forget the file.
Path 2: generate it with a route handler
Create app/llms.txt/route.ts. The directory name is the URL, so the file lands exactly at /llms.txt.
export const dynamic = 'force-static'
export async function GET() {
const posts = await getAllPosts() // your existing content source
const body = [
'# Acme Inc.',
'',
'> One or two sentences on what Acme does.',
'',
'## Guides',
...posts.map((p) => `- [${p.title}](https://acme.com/blog/${p.slug}): ${p.summary}`),
].join('\n')
return new Response(body, {
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
})
}
Route handlers support GET, and export const dynamic = 'force-static' is what makes this prerender at build instead of running per request. Next.js documents the same shape for app/data.json/route.ts in its static-export guide.
Pick one path. Two things claiming /llms.txt is a conflict you do not want to debug in production.
If you build with output: 'export'
Three things change, and all three are documented:
export const dynamic = 'force-static'stops being optional. Next.js requires it to prerender a route handler when static export is enabled.- Only
GETis supported. Fine here; there is nothing to POST to an llms.txt. headersinnext.config.jsis on the unsupported list, alongside rewrites, redirects and proxy. The handler's output is written intoout/at build time and your static host decides the content type from there, so check it with curl rather than assuming yourResponseheaders survived.
On Pages Router there is no route-handler equivalent that prerenders to a file. Write the file instead: put a scripts/gen-llms.mjs that writes public/llms.txt, then add "prebuild": "node scripts/gen-llms.mjs" to package.json. npm runs prebuild before build on its own, so CI picks it up with no extra step.
Verify it worked
Run the scan first. It answers this question and the two next to it in one shot:
npx aiscan-cli yoursite.com
Look for C2 — /llms.txt under the content dimension. The same run also grades C1 (markdown content negotiation) and C3 (structured HTML), which are the two checks a Next.js site usually fails alongside it. All three are explained on the content checks page.
What the scan cannot see: whether the URLs inside your llms.txt actually resolve, and whether the summaries are true. C2 grades the file's shape, not its honesty. Click three links yourself.
If you would rather check by hand:
curl -sI https://yoursite.com/llms.txt | head -1
curl -s https://yoursite.com/llms.txt | head -20
Expected: HTTP/2 200, a first line starting # , at least one ## heading, and at least one - [Label](https://…) line. A 200 that returns <!DOCTYPE html> means your catch-all route answered instead of the file. That is a fail, not a pass.
Keep it from going stale
Path 2 regenerates on every deploy for free. If you took Path 1, put a calendar reminder on it, or move to Path 2 once the list passes a dozen links. Worth knowing before you invest more: Ahrefs found 28% of 137,210 domains publish a valid llms.txt and 97% of those files got zero requests in May 2026, and Google states plainly that llms.txt files "won't harm (nor help) your visibility or rankings in Google Search." Six AIScan points for two minutes is a fair trade. A traffic strategy it is not. The longer argument is in Does llms.txt actually work in 2026?.
Running a store instead? The same check on a different stack is publishing llms.txt on Shopify, and the Next.js platform guide lists the rest of the checks this stack tends to miss.
Your next step
Scan the site and read the C2 row: npx aiscan-cli yoursite.com, or paste the URL at aiscan.site. If C2 already passes, the next cheapest wins on Next.js are C1 and C3. Every fix guide is indexed at aiscan.site/guides.
Frequently asked questions
Does /llms.txt have to be served as text/markdown?
No. AIScan's C2 check looks at the response status and the body: a 200, an H1, at least one ## section, and markdown links. Content type is not part of the check. Next.js and Vercel both serve their own llms.txt as text/plain; charset=utf-8, and those pass.
My llms.txt works locally but returns 404 in production. What went wrong?
Almost always the file is in the wrong directory. public/llms.txt must sit beside package.json at the project root, not inside app/ or src/app/. If you used a route handler instead, confirm the folder is app/llms.txt/ containing route.ts, and that the build log shows the route prerendered rather than skipped.
AIScan says C2 failed but curl shows my file. Why the mismatch?
You are probably testing a different host than the one scanned. Scan the exact URL you submitted, including www or apex, and follow redirects: curl -sIL https://yoursite.com/llms.txt. A 301 to another hostname, or an llms.txt that only exists on www, produces exactly this split result.
The build fails with a route conflict on /llms.txt. How do I fix it?
You have both public/llms.txt and app/llms.txt/route.ts claiming the same URL. Delete one. Keep the route handler if you want the file regenerated from your content on every deploy, and keep the static file if the list is short and rarely changes.
Do I need export const dynamic = 'force-static'?
Only when you build with output: 'export'. Next.js requires the handler to be explicitly marked static to prerender it under a static export. On a normal server or serverless deploy the line is harmless and still useful, because it pins the route to build time instead of running per request.
Can I do this on the Pages Router?
Yes, but not with a route handler, which is an App Router feature. Put the file in public/llms.txt, or generate it: write a scripts/gen-llms.mjs that emits public/llms.txt, then add "prebuild": "node scripts/gen-llms.mjs" to package.json so npm runs it before every build.
Should I also publish llms-full.txt?
Only if you have a real use for it. llmstxt.org treats the H1 as the only required part of llms.txt, and a full-text dump is a separate, much larger file to keep current. Get C2 passing first, then decide whether a full export earns its maintenance.
Will llms.txt improve my Google rankings?
No. Google's AI search optimization guidance states that maintaining llms.txt files won't harm nor help visibility or rankings in Google Search, because Google Search ignores them. Ahrefs also found 97% of the valid llms.txt files it surveyed received zero requests in May 2026. It earns 6 AIScan points cheaply; treat anything beyond that as unproven.

About the author
M Asif Rahman
Entrepreneur, curious about AI
I have been building companies for a long time, and lately most of my curiosity goes to AI. AIScan is a small personal project I built on a flight from Dubai to Narita. I use it to understand how ready the web is for AI agents, and I share what the scans teach me.
Related guides
The complete AI readiness setup for Next.js in 2026
Next.js gives you five of the files AI crawlers look for, and four of them are one file away. The setup below takes about 40 minutes on an App Router project and moves a typical Next.js site from a…
Cloudflare Agent Readiness vs Chrome's Agentic Browsing Audit in 2026: 28 Checks, One Overlap
Verified 31 August 2026. Every score, status code and audit weight below was produced on this date by running both tools live. The Lighthouse figures come from Lighthouse 13.4.1 driving Chrome for…
Your Next.js Site Ranks on Google and Is Invisible to ChatGPT: How to Prove It
Googlebot runs your JavaScript. The crawlers that build ChatGPT's and Claude's indexes, as far as anyone has measured, do not. That gap is why a React or Next.js site can hold page one on Google and…
