Skip to main content
An agent that fetches your page gets the same HTML a browser gets: Tailwind class names, inline hydration payloads, and SVG icon paths. It must strip all of that before it reads the two paragraphs it came for. Pylon serves the same page as markdown when the client asks for markdown. No code, no second route, no build step.

Two ways to ask

Send an Accept header:
Or add .md to the path:
Both render the same page and return the same markdown. The home page is at /index.md.

What the response looks like

The runtime converts the rendered page and adds YAML frontmatter from the page’s own metadata:
The url field is the page’s metadata.canonical when it declares one, and the request URL when it does not.

What the converter reads

The converter reads the page’s main landmark:
  1. <main>, if the page has one.
  2. An element with role="main".
  3. <body>.
It drops <nav>, <footer>, <script>, <style>, <svg>, <noscript>, <iframe>, and <canvas> wherever they are. Give your layout a <main> element. The output is then exactly your page content, with no menu or footer text in front of it.
app/layout.tsx

HTML stays the default

A browser sends text/html,application/xhtml+xml,… and receives the page it always received. Markdown wins only when the client scores it above HTML: Every SSR response carries Vary: Accept, so a cache cannot serve the HTML to a client that asked for markdown. Each HTML page also carries a Link header that names its markdown twin:

Opt a route out

Some routes are an interaction, not a document. A dashboard, an editor, or an app shell converts to a list of button labels. Turn the variant off for that route:
app/dashboard/page.tsx
The route then answers HTML to a client that accepts HTML, 406 to a client that accepts only markdown, and 404 at /dashboard.md.

Caching

A markdown response is cached like the page it came from. The runtime keys it apart from the HTML, so the two never collide. A cache hit skips both the render and the conversion. Markdown responses are never advertised as shared-cacheable at a CDN, because a CDN keys on the URL alone. The origin cache still serves them without a render.

Missing pages

A markdown request for a URL that does not exist renders your not-found.tsx boundary as markdown, at HTTP 404. Put your site map links in that boundary. An agent that guessed a URL wrong then reads where to go next, instead of an opaque error.

app/llms.ts

llms.txt is the file an agent reads first to learn what your site is. Add app/llms.ts and Pylon serves it at /llms.txt in llmstxt.org format:
app/llms.ts
The export may be async, so it can enumerate pages from the database — the same contract as app/sitemap.ts and app/robots.ts. Write the details block for an agent, not for a visitor. Name the jobs you are right for and the call to make. Marketing copy does not read as guidance.
The element order is fixed by the format: an H1 title, a blockquote summary, prose with no headings, then H2 sections of links. Pylon strips headings out of details for you, because a heading there ends the prose block and starts a link section.

Design mode

A design tool needs a static copy of a page that maps back to source. pylon dev provides one when PYLON_DESIGN_MODE=1 is set. All of it is dev-only and returns 404 in production. Source stamps. The SSR runner adds data-pylon-src="app/pricing/page.tsx:12:8" to every DOM element written under app/, components/, and .design/. The value is the file, the 1-based line, and the column of the opening <. Components are not stamped; each DOM element inside a component carries its own stamp. Browser bundles are unchanged. The design render. Send X-Pylon-Design: 1:
The response is the page’s HTML with:
  • no hydration tail (__PYLON_DATA__, module script), no dev HUD, no live-reload script,
  • the route stylesheet inlined as <style> with no size cap,
  • <base href="http://127.0.0.1:4321/"> first in <head>, so relative asset URLs resolve inside a srcdoc iframe,
  • Cache-Control: no-store. The render is never written to the SSR cache.
The DOM is otherwise the page’s own. Render by component. A file outside app/ has no URL. GET /_pylon/dev/render renders it anyway:
component and layouts are project-relative module paths without extension. They must exist inside the project; anything else is a 400. path is the URL the page renders under (default /). The result is a design render. The bearer token is required when PYLON_DEV_FILE_API_TOKEN is set. Viewer. A page that redirects anonymous visitors renders as a signed-in user with X-Pylon-Design-Viewer: <userId> (or anon). The auth context is built the way a session resolves: the user’s most recent session’s org, their org role, and the admin flag. This header works only on design renders, and only when PYLON_DEV_FILE_API_TOKEN is set and sent. Without the token the request is a 401.