Markdown für Agenten
Das rohe Markdown jeder Seite unter einer .md-URL oder per Accept-Content-Negotiation, eigene Serializer für deine eigenen Komponenten und die Aktionen „Als Markdown kopieren" und „Im Chat öffnen", die Leser gratis bekommen.
HTML ist für Browser. Agenten und LLMs kommen besser mit dem Markdown zurecht, in dem deine Seiten geschrieben sind — weniger Tokens, kein Drumherum und Komponenten in einer Form, die ein Modell lesen kann. Blume liefert dieses Markdown für jede Seite aus, in der Entwicklung wie in der Produktion, ganz ohne Konfiguration.
Rohes Markdown
Hänge .md oder .mdx an die URL einer beliebigen Seite an, um ihren rohen Markdown-Quelltext abzurufen — perfekt für LLMs, Coding-Agenten und „Als Markdown kopieren”-Workflows.
| URL | Liefert |
|---|---|
/quickstart |
Die gerenderte Seite |
/quickstart.md |
Reines Markdown, mit konvertierten Komponenten |
/quickstart.mdx |
Den MDX-Quelltext, Komponenten wie geschrieben |
Verschachtelte Routen funktionieren genauso (/content/syntax.md), und die Startseite wird unter /index.md ausgeliefert.
Die .md-Variante reduziert Komponenten auf reines Markdown für Konsumenten, die kein JSX interpretieren können: <TypeTable> wird zu einer Markdown-Tabelle, <Callout> zu einem beschrifteten Blockzitat, <Steps> zu einer nummerierten Liste, <Tabs> zu fett beschrifteten Abschnitten, <Card> zu seinem Titel als Link über seinem Inhalt (und <CardGroup> zu den Karten, die sie enthält), <Accordion> zu fett gesetzten Fragen über ihren Antworten, <FileTree> zu seiner Liste, <CodeGroup> zu seinen betitelten Code-Blöcken, <YouTube> zu einem Link und jede andere eingebaute Komponente — Columns, Frame, Expandable, Badge, Tooltip und der Rest — zu ihrem lesbaren Inhalt. <AutoTypeTable>, das den Type-Checker braucht, bleibt unverändert, ebenso ein <Diff>, das seine beiden Seiten aus Dateien liest (src oder before und after), und ein <GithubInfo> ohne owner und repo. Auch die Komponenten, aus denen eine generierte API-Referenzseite besteht, werden reduziert: <Operation> wird zum Endpunkt in der Notation der jeweiligen Spezifikation (GET /pets/{id}, SEND user/signup, query pets) mit einer Deprecation-Markierung und, bei einer OpenAPI-Operation, den Eigenschaften und dem Beispiel ihres Request-Bodys sowie ihren Antworten samt deren Beispielen, <ApiTagOperations> zu einer Liste dieser Endpunkte, verlinkt auf ihre Seiten und mit ihren Zusammenfassungen, und <ApiOverview> zur Version und den Basis-URLs der API — so weiß ein Agent, der eine Referenzseite liest, was er aufrufen muss, und die Seitensuche findet den Pfad eines Endpunkts. <Changelog /> wird zu der Release-Liste, die es rendert: Jahresüberschriften über je einer verlinkten Zeile pro Release. Props werden als literale Daten gelesen und nie ausgeführt — Strings, Zahlen, Booleans, Arrays, Objekte und Template-Strings sowie Verweise auf das frontmatter der Seite —, sodass ein Prop wie title={frontmatter.status} denselben Wert ergibt, den auch die gerenderte Seite zeigt. Nur Props werden aufgelöst: Ein Ausdruck im eigenen Text der Seite, etwa Owned by {frontmatter.owner}, bleibt in der Markdown-Kopie unverändert. Für einen Wert, den auch Agenten lesen sollen, schreib ihn direkt in den Text — oder, wenn er auf jeder Seite gleich ist, mach ihn zu einer Inhaltsvariable, die auf jeder Oberfläche ersetzt wird. Alles, was sich nicht originalgetreu konvertieren lässt — eine eigene Komponente oder ein Prop, das durch einen Funktionsaufruf oder aus einem Import berechnet wird — bleibt unverändert, und Komponenten-Markup innerhalb von Code, ob als Block oder inline, wird nie angefasst. Dieselbe Konvertierung gilt für llms-full.txt und das get_page-Tool des MCP-Servers, sodass jede an Agenten gerichtete Oberfläche sauberes Markdown liefert.
Wenn du das MDX selbst möchtest, nutze die .mdx-Variante: Ihre Komponenten bleiben wie geschrieben, und der Rest wird wie bei der .md-Variante für einen Agenten aufbereitet, der sie über ihre URL liest. Includes werden eingefügt, <Visibility> wird für Agenten aufgelöst (Inhalt mit for="web" wird entfernt, Inhalt mit for="agents" bleibt erhalten), relative Bilder und Links auf andere Dateien neben der Seite verweisen auf ihre ausgelieferten URLs, relative Seitenlinks und wurzelrelative Links auf eine Inhaltsdatei (/guides/setup.md) auf die Routen, die sie meinen, und wurzelrelative Links (/guides/install) erhalten deployment.base und basePath der Site und wandern auf einer übersetzten Seite in die Sprache der Seite — so wie es die Links der gerenderten Seite tun. Wurzelrelative Bilder und Links auf Dateien in public/ (/spec.pdf) erhalten nur deployment.base, da diese Dateien dort ausgeliefert werden.
Content Negotiation
Agenten müssen die .md-Konvention nicht kennen: Ruft man die eigene URL einer Seite mit einem Accept: text/markdown-Header ab, wird die Markdown-Variante unter derselben Adresse ausgeliefert, mit Vary: Accept, damit Caches beide auseinanderhalten. Der Dev-Server berücksichtigt den Header von Haus aus, und ein Vercel- oder Cloudflare-Server-Build verdrahtet dieselbe Negotiation automatisch ins Deployment — Routing-Regeln auf Vercel, ein generierter Worker auf Cloudflare — ohne jede Konfiguration. Die Startseite verhandelt immer, selbst wenn sie eine eigene Landingpage statt einer Inhaltsseite ist: Ihr Markdown-Pendant fällt auf den llms.txt-Index zurück, sodass ein Agent, der die Site-Wurzel nach Markdown fragt, die maschinenlesbare Karte der Site erhält. Markdown-Antworten tragen außerdem einen x-markdown-tokens-Header — eine geschätzte Token-Anzahl (~4 Zeichen pro Token), nach der Konvention von Cloudflares Markdown for Agents — auf jeder Oberfläche, auf der Blume die Response-Header kontrolliert: dem Dev-Server, servergerenderten Antworten und der verhandelten Startseite auf Vercel und Cloudflare. Andere Deploy-Ziele liefern vorgerenderte Seiten aus einer statischen Schicht ohne Hook zur Request-Zeit aus, dort rufen Agenten also die .md-URL direkt ab; das Agent-Readability-Manifest weist contentNegotiation nur auf Deployments aus, die den Header berücksichtigen.
Ein Client bekommt Markdown, wenn sein Accept-Header text/markdown (oder text/x-markdown, unabhängig von der Groß- und Kleinschreibung) mit einem q-Wert über null aufführt, der mindestens so hoch ist wie der von text/html: text/markdown und text/markdown, text/html bekommen Markdown, text/html, text/markdown;q=0.9 bekommt HTML. Die Routing-Regeln von Vercel können keine zwei q-Werte miteinander vergleichen; tragen also beide Typen einen Wert unter 1 (text/markdown;q=0.9, text/html;q=0.8), liefert Vercel HTML aus, wo der Dev-Server und Cloudflare Markdown ausliefern. Sende text/markdown ohne q-Wert, um überall Markdown zu bekommen.
Auch fehlende Seiten verhandeln. Die Standard-404-Seite hat ein Markdown-Pendant unter /404.md — die Nicht-gefunden-Meldung, gefolgt von Wiederherstellungslinks zu jedem Abschnitt der obersten Ebene, zur Sitemap und zu llms.txt — und bei einem Vercel- oder Cloudflare-Server-Build erhält eine Anfrage an eine nicht existierende URL, die Markdown bevorzugt, diesen Inhalt mit einem echten 404-Status statt der HTML-Hülle. Auf Vercel gilt dasselbe für jede .md-URL ohne dahinterliegende Seite. Eine eigene 404-Seite (eine pages/404.astro oder eine Inhaltsseite unter /404) ersetzt die Standardseite samt ihrem /404.md-Pendant, fehlende Seiten antworten dann also mit deiner HTML-Seite.
Eigene Komponenten-Serializer
Gib deinen eigenen Komponenten eine Markdown-Form mit agents.markdownComponents — einer Zuordnung von JSX-Name zu Serializer. Jeder Serializer erhält die props der Komponente (aus den MDX-Attributen als literale Daten gelesen, mit aufgelösten frontmatter-Verweisen), ihre children (bereits auf Markdown reduziert) und die frontmatter-Daten der Seite und gibt den Ersatz zurück — oder null, um das JSX unverändert zu lassen:
import { defineConfig } from "blume";
import type { ComponentMarkdown } from "blume";
const chart: ComponentMarkdown = ({ props }) =>
``;
export default defineConfig({
agents: {
markdownComponents: {
Chart: chart,
},
},
});
Für Container-Komponenten extrahiert childComponents("Name") direkte Kinder anhand ihres Tags — genauso, wie der eingebaute <Steps>-Serializer seine <Step>-Elemente einsammelt — und childBlocks() gibt jedes direkte Kind der Reihe nach zurück, Komponenten wie Fließtext, jeweils bereits zu einem Markdown-Block reduziert (der eingebaute <CardGroup>-Serializer ist einfach nur diese Blöcke, verbunden durch Leerzeilen). Ein Eintrag mit gleichem Namen ersetzt einen eingebauten Serializer, du kannst also umgestalten, wie <Callout> reduziert wird — oder null zurückgeben, um eine Komponente ganz auszunehmen.
Serializer leben in blume.config.ts, nicht in components.tsx: Die Konfigurationsdatei wird zur Build-Zeit ausgeführt, während die Komponentendatei nur statisch analysiert wird (sie darf .astro-Dateien importieren, die außerhalb des Site-Builds nicht laufen können). Deine Komponenten selbst bleiben exakt wie zuvor in components.tsx registriert — markdownComponents fügt nur ihre an Agenten gerichtete Markdown-Form hinzu.
Als Markdown kopieren
Jede Seite trägt eine Aktion Als Markdown kopieren — in den Seitenaktionen unterhalb des Inhaltsverzeichnisses —, die das rohe Markdown der Seite in die Zwischenablage kopiert. Es ist derselbe Quelltext, der unter der .md-URL oben ausgeliefert wird, bereit zum Einfügen in ein LLM, ein Issue oder deine Notizen. Sie ist auf jeder Seite verfügbar, in der Entwicklung wie in der Produktion, ganz ohne Konfiguration.
Wo die Clipboard-API nicht verfügbar ist oder der Browser sie verweigert — In-App-Browser, WebViews, unsichere Origins —, fällt die Aktion auf den alten Kopierbefehl zurück, und wenn nichts in der Zwischenablage landet, meldet der Button Kopieren fehlgeschlagen (lokalisiert über actions.copyFailed), statt stumm zu bleiben. Derselbe Fallback steht hinter jedem Kopier-Button, den Blume rendert.
Im Chat öffnen
Die Aktion Im Chat öffnen öffnet die aktuelle Seite in einem KI-Assistenten — v0, ChatGPT, Claude, T3 Chat, Scira oder Cursor — vorausgefüllt mit einem Prompt, der ihn auf das rohe Markdown der Seite verweist, damit er Fragen zu dem beantworten kann, was du gerade liest:
Lies
https://your-site/this-page.md, damit ich dir Fragen zu dieser Seite stellen kann.
Wie „Als Markdown kopieren” braucht sie keinerlei Einrichtung. Der Assistent ruft die Seite über ihre öffentliche URL ab, sie funktioniert also, sobald die Seite deployt ist.
Der Prompt ist Teil des UI-Wörterbuchs (actions.openInChatPrompt), lokalisierte Sites senden ihn also in ihrer Sprache, und i18n.ui kann die Formulierung überschreiben — behalte dabei den Platzhalter {url}, der durch die Roh-Markdown-URL der Seite ersetzt wird.
Um die Aktion anzupassen, setze ai.openInChat. false blendet sie vollständig aus, und ein Array von Provider-Schlüsseln — "v0", "chatgpt", "claude", "t3", "scira", "cursor" — zeigt genau diese Provider, in der von dir angegebenen Reihenfolge:
ai: {
openInChat: ["claude", "chatgpt", "cursor"],
}
Um einen fertig kopierbaren Prompt direkt in deine Inhalte einzubetten — statt einer seitenweiten Aktion — nutze die Prompt-Komponente, die eine beschriftete Zeile mit einem Prompt kopieren-Button und einem optionalen In-Cursor-öffnen-Link rendert.