# Adding llms.txt to Next.js

> Add llms.txt to Next.js as a static file in public/ or as a route handler that builds it from your content on every deploy, for the App and Pages Routers.

Source: https://llmstextgenerator.com/llms-txt-nextjs/
Last updated: 7 October 2026

The easiest platform of the three. Either drop a file in `public/`, or add a route handler that builds the file from your content so it can never drift out of date.

**In Next.js, save the file as `public/llms.txt` and it is served at `/llms.txt` with the right content type.** For content-driven sites, a route handler that generates it at build time is better, because it stays current on every deploy.

## Option 1: a static file

Anything in the [`public` folder](https://nextjs.org/docs/app/api-reference/file-conventions/public-folder) is served from the root. Save your file as `public/llms.txt` and it is live at `/llms.txt`. Nothing else to configure.

That is the right answer for a marketing site whose structure rarely changes. For anything content-driven, generate it.

## Option 2: a route handler (App Router)

Create a [route handler](https://nextjs.org/docs/app/api-reference/file-conventions/route) at `app/llms.txt/route.ts`. With `dynamic = 'force-static'` it runs at build time, so the file is generated from your real content on every deploy and cannot go stale.

app/llms.txt/route.ts

```
import { getAllDocs } from '@/lib/content';

export const dynamic = 'force-static';

const SITE = 'https://example.com';

export async function GET() {
  const docs = await getAllDocs();

  const bySection = docs.reduce<Record<string, typeof docs>>((acc, doc) => {
    (acc[doc.section] ||= []).push(doc);
    return acc;
  }, {});

  const lines: string[] = [
    '# Lumen CLI',
    '',
    '> Command-line tool for managing container deployments. Open source,',
    '> MIT licensed. Current stable release is 4.2.',
    '',
  ];

  for (const [section, entries] of Object.entries(bySection)) {
    lines.push(`## ${section}`);
    for (const doc of entries) {
      lines.push(`- [${doc.title}](${SITE}${doc.path}): ${doc.summary}`);
    }
    lines.push('');
  }

  return new Response(lines.join('\n'), {
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
      'Cache-Control': 'public, max-age=3600',
    },
  });
}
```

**The `summary` field is where the quality lives.** If it falls back to the first sentence of the page, you get a mediocre file automatically. Add a deliberate one-line summary to your content front matter and the same code produces a good one.

## What about the Pages Router?

Use `getServerSideProps` in `pages/llms.txt.tsx` to write the response directly:

pages/llms.txt.tsx

```
import type { GetServerSideProps } from 'next';
import { buildLlmsTxt } from '@/lib/llms';

export const getServerSideProps: GetServerSideProps = async ({ res }) => {
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  res.setHeader('Cache-Control', 'public, max-age=3600');
  res.write(await buildLlmsTxt());
  res.end();
  return { props: {} };
};

export default function LlmsTxt() {
  return null;
}
```

## Adding llms-full.txt and V2 links

Use the same pattern at `app/llms-full.txt/route.ts`, emitting each document’s body instead of its summary. Keep the source list narrow (documentation only) and watch the output size, since a full-text file over roughly 500 KB stops being loadable. To follow [llms.txt V2](https://llmstextgenerator.com/llms-txt-v2/), add `<link rel="describedby" href="/llms.txt">` in your root layout’s metadata, and serve Markdown versions of pages if your content is already Markdown. The [llms-full.txt guide](https://llmstextgenerator.com/what-is-llms-full-txt/) covers the trade-offs.

## How do you check it works?

Run a production build rather than trusting dev mode, then check the response headers:

```
npm run build && npm start
curl -sI http://localhost:3000/llms.txt | head -n 3
```

You want `200` and `content-type: text/plain`. After deploying, run the live URL through the [validator](https://llmstextgenerator.com/llms-txt-validator/).

## Frequently asked questions

Static file or route handler?

A static file for a small marketing site. A route handler for anything with a content collection, because it removes the drift problem completely.

Will it work on Vercel and other hosts?

Yes. A force-static route handler is emitted as a static file at build time, so it works anywhere the build output is served.

Does this work in Astro, Nuxt or SvelteKit?

The same two patterns apply. Each has a public directory and a way to define a custom route returning plain text.

Should I cache it?

An hour is a sensible max-age. It is a small file that changes on deploy, so there is little to gain from aggressive caching.
