LLM Outputs

llms.txt, llms-full.txt, per-page Markdown, the page actions menu, and the llm config.

Documentation is read by assistants as often as by people. PzzaDocs produces three plain-text outputs from the same content so agents can index the site without scraping HTML, and surfaces them to readers through a page actions menu.

Outputs#

OutputSource methodContents
/llms.txtgetLlmsTxt()An index of every page with its title, URL, and description, organised by the page tree.
/llms-full.txtgetLlmsFullTxt()Every page's Markdown concatenated in tree order.
<page>.mdxgetMarkdown(slug)One page as raw Markdown with frontmatter stripped, the title as an H1, and [#id] anchors after each heading.

The anchors in the per-page Markdown match the rendered heading ids, so an assistant can deep-link into the HTML page it was reading in text form.

Routes#

Add two route handlers for the site-wide files:

app/llms.txt/route.ts
import { source } from "@/lib/source";

export const dynamic = "force-static";

export async function GET() {
  return new Response(await source.getLlmsTxt(), {
    headers: { "content-type": "text/plain; charset=utf-8" },
  });
}
app/llms-full.txt/route.ts
import { source } from "@/lib/source";

export const dynamic = "force-static";

export async function GET() {
  return new Response(await source.getLlmsFullTxt(), {
    headers: { "content-type": "text/plain; charset=utf-8" },
  });
}

Per-page Markdown needs its own segment, because a route handler cannot live next to page.tsx inside app/docs/[[...slug]]:

app/llms.mdx/[[...slug]]/route.ts
import { source } from "@/lib/source";

export const dynamic = "force-static";

export async function generateStaticParams() {
  const pages = await source.getPages();
  return pages.map((page) => ({ slug: page.slug }));
}

export async function GET(_request: Request, context: { params: Promise<{ slug?: string[] }> }) {
  const { slug = [] } = await context.params;
  const markdown = await source.getMarkdown(slug);
  if (!markdown) {
    return new Response("Not found", { status: 404 });
  }
  return new Response(markdown, { headers: { "content-type": "text/markdown; charset=utf-8" } });
}

Linking the menu to the route#

Set siteUrl in the config (this site uses https://docs.pzza.works) so the links in llms.txt and llms-full.txt and the prompts behind "Open in Claude", "Open in ChatGPT" and "Open in Cursor" are absolute; pass it to DocsPage as siteUrl={config.siteUrl}. Without it the files use relative paths and the menu falls back to the browser origin.

llm.markdownPath maps a page URL to its Markdown URL. The default appends .mdx to the page URL (/docs/guides/llm becomes /docs/guides/llm.mdx, and the index /docs becomes /docs.mdx). You have two options to make that URL resolve:

Keep the default and add a proxy.ts that rewrites .mdx requests under baseUrl to the handler. This is what this site does, so both /docs/guides/llm.mdx and /llms.mdx/guides/llm work.

proxy.ts
import { NextResponse, type NextRequest } from "next/server";

const BASE_URL = "/docs";
const MARKDOWN_ROUTE = "/llms.mdx";

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;
  if (pathname.endsWith(".mdx")) {
    const page = pathname.slice(0, -".mdx".length);
    if (page === BASE_URL || page.startsWith(`${BASE_URL}/`)) {
      const url = request.nextUrl.clone();
      url.pathname = `${MARKDOWN_ROUTE}${page.slice(BASE_URL.length)}`;
      return NextResponse.rewrite(url);
    }
  }
  return NextResponse.next();
}

Page actions#

Pass the computed Markdown URL to DocsPage. When markdownUrl or editUrl is present, a toolbar appears above the article with a Copy Markdown button and a View options menu containing Open in GitHub, View as Markdown, and open-in-assistant links that prefill a prompt with the page URL.

app/docs/[[...slug]]/page.tsx
<DocsPage
  toc={page.toc}
  tree={tree}
  url={page.url}
  editUrl={config.editUrl?.(page)}
  markdownUrl={config.llm.enabled ? config.llm.markdownPath(page.url) : undefined}
>

The toolbar is the PageActions component from pzzadocs/ui; it can also be rendered on its own:

tsx
import { PageActions } from "pzzadocs/ui";

<PageActions markdownUrl="/guides/llm.mdx" editUrl="https://github.com/pzzaworks/pzza-docs" pageUrl="/guides/llm" />
NameTypeDefaultDescription
markdownUrlstring-Route serving the page as raw Markdown. Enables Copy Markdown and the assistant links.
editUrlstring-Open in GitHub target.
pageUrlstring-Current page route, used in the assistant prompt when markdownUrl is missing.

Config#

NameTypeDefaultDescription
llm.enabledbooleantrueTurns the outputs and the page actions menu on. The route handlers are yours, so disabling this only hides the menu.
llm.markdownPath(url: string) => string(url) => `${url}.mdx`Maps a page URL to the URL serving its Markdown.

Try it#