LLM Content Negotiation
Give AI agents first-class Markdown at the same URLs your readers use. pracht routes can negotiate on Accept: text/markdown, publish /llms.txt, and keep HTML as the browser default.
One URL, Two Representations
pracht can serve the same route as either a normal HTML page or raw Markdown. Browsers keep receiving rendered HTML, while agents that explicitly ask for Markdown get the source document without navigation chrome, hydration state, or scraped layout noise.
# Human-readable HTML
curl https://pracht.resynapse.dev/docs/routing
# Agent-readable Markdown
curl -H "Accept: text/markdown" https://pracht.resynapse.dev/docs/routingThe response includes Content-Type: text/markdown; charset=utf-8 and Vary: Accept, so caches keep the HTML and Markdown variants separate.
Opt In with a Markdown Export
Any route can expose an agent version by exporting a markdown string. When the incoming request prefers text/markdown, pracht returns that string before running the normal render pipeline.
export const markdown = `# Pricing
- Starter: free
- Pro: usage-based
- Enterprise: contact sales
`;
export function Component() {
return <PricingPage />;
}For the docs site, the Markdown route plugin emits that export automatically for every .md page:
export const markdown = rawSource;That means every documentation page is already an LLM-friendly endpoint.
Accept Header Rules
pracht only switches to Markdown when the client explicitly prefers it. Browser-style wildcards like */* still receive HTML.
| Request header | Result |
|---|---|
Accept: text/html |
Rendered HTML |
Accept: */* |
Rendered HTML |
Accept: text/markdown |
Raw Markdown |
Accept: text/html;q=0.8, text/markdown;q=1.0 |
Raw Markdown |
Accept: text/html;q=1.0, text/markdown;q=0.5 |
Rendered HTML |
This makes the feature safe to enable on public pages: humans get the polished app, and agents get a deterministic source format when they ask for it.
Discovery with llms.txt
Content negotiation is paired with two discovery files in this example app:
/llms.txt— a concise map of the docs with titles, descriptions, and canonical URLs./llms-full.txt— a single Markdown bundle with the full source of every listed page.
curl https://pracht.resynapse.dev/llms.txt
curl https://pracht.resynapse.dev/llms-full.txtThe docs Vite config wires those files with a tiny plugin that scans the route manifest and frontmatter:
llmsTxt({
origin: "https://pracht.resynapse.dev",
routesFile,
title: "pracht",
description:
"A full-stack Preact framework built on Vite with hybrid rendering and a unified data-loading model.",
sections: [{ heading: "Docs", match: "/docs" }],
});Agents can start at /llms.txt, follow the canonical route URLs, and request any page with Accept: text/markdown when they need exact source.
Why It Helps
- No scraping required — agents receive source Markdown instead of parsing rendered UI.
- Canonical URLs stay canonical — the same page URL works for humans, crawlers, and LLM tools.
- Static pages still work — adapters skip static HTML asset serving for Markdown requests so SSG routes can negotiate through the framework.
- Cache-safe by default —
Vary: Acceptseparates the HTML and Markdown responses. - Framework-native — hand-authored
.tsxroutes and transformed.mdroutes use the samemarkdownexport contract.