Markdown für Agenten: Accept: text/markdown ausliefern

Ein Agent fragt Ihre Seite an und bekommt HTML. Dann wirft er das meiste davon weg: Navigation, Fußzeile, Cookie-Banner, Inline-Skripte, Klassenattribute, SVG-Symbole. Übrig bleiben ein paar Absätze Text.

Die Version, die er wollte, haben Sie längst. Fast niemand liefert sie aus.

Was das Markup kostet

Vergleichen Sie, was Sie senden, mit dem, was übrig bleibt:

curl -s https://beispiel.de/about | wc -c
curl -s -H "Accept: text/markdown" https://beispiel.de/about | wc -c

Sind beide Zahlen gleich, liefern Sie allen dieselbe Repräsentation aus und das Aufräumen erledigt der Agent. Diese Arbeit ist nicht umsonst. Alles im <head>, jedes umschließende <div>, jedes data--Attribut und jedes Analytics-Snippet landet im Kontextfenster, bevor irgendetwas entscheidet, dass es Rauschen war. Auf einer Seite, deren eigentlicher Inhalt eine Überschrift und acht Absätze sind, ist der größte Teil der Bytes Gerüst.

Der Markdown-Zwilling behält Überschriftenebenen, Links, Listen, Tabellen und Codeblöcke. Er verliert Navigation, Gestaltung und Skripte, und davon wäre ohnehin nie etwas zitiert worden.

Wie die Aushandlung über Accept funktioniert

Der Client sagt, was er verwerten kann:

GET /about HTTP/1.1
Host: beispiel.de
Accept: text/markdown

Der Server wählt für dieselbe URL eine Repräsentation:

HTTP/1.1 200 OK
Content-Type: text/markdown; charset=utf-8
Vary: Accept

Ein Browser sendet Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 und bekommt HTML. Ein Client, der text/markdown verlangt, bekommt Markdown. Die kanonische URL bleibt dieselbe, es gibt also weiterhin nur eine Adresse zum Verlinken und eine Seite zum Teilen.

Vary: Accept, oder wie ein Browser eine Textdatei herunterlädt

Vary ist nicht optional, und genau diesen Teil lassen die meisten weg.

Jeder Cache zwischen Ihnen und dem Leser (Ihr CDN, ein Reverse Proxy, der Browser-Cache selbst) schlüsselt Antworten nach URL. Liefern Sie zwei verschiedene Rümpfe unter einer URL aus, ohne das anzukündigen, gilt die zuerst gecachte Antwort für alle. Ein Agent fragt /about mit Accept: text/markdown an, Ihr CDN legt das Markdown ab, und der nächste Mensch, der /about öffnet, sieht eine Seite voller Sternchen oder einen Download-Dialog.

Vary: Accept

Diese Zeile weist den Cache an, den Accept-Header in den Schlüssel aufzunehmen, sodass HTML und Markdown getrennt gespeichert werden. Senden Sie sie in beiden Antworten, nicht nur in der Markdown-Antwort.

Das hat einen realen Preis: Accept-Header unterscheiden sich stark zwischen Clients, eine Variation darüber zersplittert also Ihren Cache. Das ist ein ehrliches Argument für die folgende Alternative.

Der .md-Zwilling

Die einfachere Variante ist eine zweite URL. /about liefert HTML, /about.md liefert denselben Inhalt als Markdown. Keine Aushandlung, kein Vary, nichts, was in einem Cache schiefgehen kann. Es ist eine ganz normale URL, also verlinkbar, als Lesezeichen speicherbar, in die Sitemap eintragbar und von jedem Client abrufbar, auch von einem, der überhaupt keinen brauchbaren Accept-Header schickt.

Sagen Sie dann, dass es sie gibt. Im HTML:

<link rel="alternate" type="text/markdown" href="https://beispiel.de/about.md">

Und in den Antwort-Headern, für alles, was ein HEAD macht und den Rumpf nie parst:

Link: <https://beispiel.de/about.md>; rel="alternate"; type="text/markdown"

Beides zusammen kostet je eine Zeile und deckt die Clients ab, die nur an einer Stelle nachsehen.

Prüfen Sie Ihre eigene Seite

# 1. Antwortet dieselbe URL unterschiedlich?
curl -s -H "Accept: text/markdown" https://beispiel.de/about | head -20

# 2. Gibt die HTML-Antwort zu, dass der Zwilling existiert?
curl -sI https://beispiel.de/about | grep -i "vary\|link"

# 3. Funktioniert die .md-URL für sich allein?
curl -sI https://beispiel.de/about.md | grep -i "content-type\|vary"

Der zweite Befehl fängt den üblichen Fehler ab. Eine Seite kann einen tadellosen .md-Zwilling haben, den nie jemand findet, weil kein Header und kein Tag darauf zeigt.

Was wir hier tun

Jede Seite dieser Website hat einen Markdown-Zwilling, und packages/web/src/lib/content-negotiation.js ist das Ganze: rund neunzig Zeilen.

Eine feste Tabelle listet die statischen Pfade und ihre Zwillinge: / → /index.md, /about → /about.md, jede Tool-Seite und so weiter. Pfade mit einer ID oder einem Slug (Berichte unter /r/<uuid>, Leitfäden, Blogbeiträge wie dieser) werden stattdessen über ein Muster erkannt und bekommen <pfad>.md.

Die Prüfung der Anfrage ist absichtlich grob: Enthält der Accept-Header die Zeichenkette text/markdown, wird Markdown gesendet. Keine Gewichtung nach q-Werten. Ein Browser sendet diese Zeichenkette nie, in der Praxis trennt das die beiden Fälle also sauber, aber ein Client, der text/markdown mit niedriger Priorität aufführt, bekommt trotzdem Markdown.

Jede Markdown-Antwort geht mit diesen Headern hinaus:

Content-Type: text/markdown; charset=utf-8
Content-Signal: search=yes, ai-input=yes, ai-train=no
Vary: Accept
X-Markdown-Tokens: 4213
Cache-Control: public, max-age=3600

Content-Signal sagt, was mit dem Inhalt geschehen darf, und nicht nur, ob er abgerufen werden darf. X-Markdown-Tokens ist eine Größenangabe: Sie trägt heute die Zeichenzahl des Rumpfs, keine echte Token-Zahl.

HTML-Antworten einer Seite mit Zwilling bekommen Vary: Accept und den Link-Header, und in den <head> kommt das rel="alternate"-Tag aus derselben Tabelle.

Dieses letzte Detail hat uns gebissen. Die Tabelle speiste das Template, aber die über Muster erkannten Routen standen nicht in der Tabelle. Jeder Analysebericht lieferte also seinen Markdown-Zwilling aus und kündigte ihn im Link-Header an, während sein HTML nichts davon sagte. Der Zwilling existierte und war für alles unsichtbar, was das Markup las.

Eine Konvention, kein Standard

Accept: text/markdown ist gewöhnliche HTTP-Content-Negotiation, und die ist ein Standard. Sie für Markdown zu nutzen, ist eine Konvention, die aus dem llms.txt-Vorschlag entstanden ist, und kein Crawler ist verpflichtet, danach zu fragen. Ehrlich gesagt ist der .md-Zwilling der Teil, der verlässlich funktioniert, weil er eine URL wie jede andere ist und alles ihn abrufen kann, während die Aushandlung einen Header kostet und sich bei den Clients auszahlt, die tatsächlich fragen. Schaden wird Ihnen keins von beidem, und was genutzt wird, erfahren Sie aus Ihren Logs, nicht aus Ankündigungen.

Testen Sie Ihre Markdown-Aushandlung · Erzeugen Sie Ihre llms.txt