afaqrashid.com  ·  portfolio · 2026 ed.

llms.txt in Next.js: Static File or Route Handler, Tested

There are two ways to add llms.txt to a Next.js App Router site. Put a static file at public/llms.txt, or generate it from your content with a route handler at app/llms.txt/route.ts. If you generate it, add export const dynamic = "force-static", or it's rebuilt on every request.

I tested both on a fresh Next.js 16.4.4 app. First, though, be clear on what it does and doesn't do.

What llms.txt is, and what it isn't

llms.txt is a proposal, not a standard. It's a Markdown file at the root of a site that gives language models a short summary and a list of the pages worth reading.

It won't help you in Google. Google's page on AI features says "You don't need to create new machine readable files, AI text files, or markup to appear in these features." I keep one on this site because it's cheap and some tools read it, not because it ranks anything.

Option 1: a static file in public/

Write the file by hand and save it as public/llms.txt. Next.js served mine at /llms.txt with Content-Type: text/plain; charset=UTF-8, so accented characters came through fine.

The catch is upkeep. Every new page means editing the file, and it's easy to forget.

Option 2: generate it with a route handler

A folder named llms.txt with a route.ts inside serves /llms.txt. Build the file from the same data your pages use:

// app/llms.txt/route.ts
import { POSTS } from "@/data/posts";

export const dynamic = "force-static";

export function GET() {
  const body = [
    "# Example",
    "",
    "> A short summary of the site.",
    "",
    "## Posts",
    "",
    ...POSTS.map((p) => `- [${p.title}](https://example.com/blog/${p.slug}): ${p.description}`),
    "",
  ].join("\n");

  return new Response(body, {
    headers: { "Content-Type": "text/plain; charset=utf-8" },
  });
}

Here's what the build printed for two route handlers, one with force-static and one without:

├ ƒ /llms-full.txt   no config: runs on every request
└ ○ /llms.txt        force-static: built once

The static one came back with x-nextjs-cache: HIT and cache-control: s-maxage=31536000. The other ran its code for every visitor. For a file that only changes when you deploy, force-static is the right choice.

Don't keep both

With public/llms.txt and app/llms.txt/route.ts in the same project, the production server returned the file from public/. The route handler was ignored without an error. I saw the same with robots.txt and favicon.ico, so delete the old static file when you switch to generating it.

Make sure crawlers can reach it

If your proxy.ts or middleware redirects signed-out visitors, exclude llms.txt in its matcher, as you would robots.txt and sitemap.xml. Then check it from outside:

curl -sI https://example.com/llms.txt | grep -iE "^HTTP|content-type"

You want a 200 and text/plain. A 307 means something redirected it.

Related

Sources