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#
| Output | Source method | Contents |
|---|---|---|
/llms.txt | getLlmsTxt() | An index of every page with its title, URL, and description, organised by the page tree. |
/llms-full.txt | getLlmsFullTxt() | Every page's Markdown concatenated in tree order. |
<page>.mdx | getMarkdown(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:
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" },
});
}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]]:
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.
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();
}Point markdownPath straight at the handler and skip the rewrite:
llm: {
markdownPath: (url) => url.replace(/^\/docs/, "/llms.mdx"),
},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.
<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:
import { PageActions } from "pzzadocs/ui";
<PageActions markdownUrl="/guides/llm.mdx" editUrl="https://github.com/pzzaworks/pzza-docs" pageUrl="/guides/llm" />| Name | Type | Default | Description |
|---|---|---|---|
markdownUrl | string | - | Route serving the page as raw Markdown. Enables Copy Markdown and the assistant links. |
editUrl | string | - | Open in GitHub target. |
pageUrl | string | - | Current page route, used in the assistant prompt when markdownUrl is missing. |
Config#
| Name | Type | Default | Description |
|---|---|---|---|
llm.enabled | boolean | true | Turns 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#
- /llms.txt
- /llms-full.txt
- /docs/guides/llm.mdx, this page as Markdown