Servere Markdown til boter med innholdsforhandling
En nettside og informasjonen inni den er ikke det samme. Siden er en leveringsmekanisme (layout, skript, hydreringsmarkører, et tre av innpakningselementer) pakket rundt en mye mindre nyttelast av faktisk innhold. Mennesker trenger leveringsmekanismen. En KI-crawler gjør det ikke. Den vil ha innholdet, og alt annet er overhead den betaler for ved hvert eneste kall og deretter kaster.
Dette er kjerneinnsikten bak å sende med en Markdown-følgesvenn for hver side. På et statisk
prerendret nettsted, som dette, er følgesvennene bare filer: hver rute rendres til HTML på
byggetidspunktet, og en tilhørende .md legges ved siden av, annonsert gjennom en
text/markdown-alternativlenke i head og indeksert i llms.txt. Det er ingen server i
forespørselsløpet, så det er ingenting å optimalisere ved kjøretid.
Det interessante tilfellet er server-side rendering, der hvert sidevisning koster deg en
reell rendering. Der slutter Markdown-følgesvennen å være en bekvemmelighet og blir en
mekanisme for å avlaste server, så fremt du serverer den gjennom innholdsforhandling i
stedet for kun på en egen .md-URL.
Hva innholdsforhandling gir deg
Innholdsforhandling er HTTP-mekanismen for å servere ulike representasjoner av samme
ressurs på samme URL, valgt ut fra det klienten signaliserer. Det kanoniske signalet er
Accept-headeren. En nettleser sender Accept: text/html; en crawler som forstår ordningen
kan sende Accept: text/markdown og få den lette representasjonen tilbake fra den identiske
URL-en: ingen egen lenke å oppdage, ingen ekstra URL å vedlikeholde.
Knepet er å respektere kun et eksplisitt signal:
// Sann kun når klienten eksplisitt oppgir text/markdown. Et jokertegn (*/*)
// eller text/html forblir HTML, så nettlesere, som aldri ber om markdown,
// er fullstendig upåvirket.
function acceptsMarkdown(req: Request): boolean {
const accept = req.headers["accept"];
const acceptStr = Array.isArray(accept) ? accept[0] : accept;
return /\btext\/markdown\b/i.test(acceptStr ?? "");
}
I praksis sender de fleste KI-crawlere ennå ikke Accept: text/markdown, så et annet signal
gjør den tunge jobben: User-Agent. Hvis du gjenkjenner crawleren ved navn, kan du servere
den Markdown kategorisk, uten at den trenger å samarbeide om headere.
En mellomvare som griper inn før rendereren
Hele greia får plass i én Express-mellomvare montert foran både den statiske håndtereren og Angular SSR-motoren. Vi kjører en variant av dette på et søsternettsted; formen er:
server.use(compression());
// Server markdown til KI-boter FØR Angular i det hele tatt rendrer.
// Mellomvaren kortslutter forespørselen for kjente crawlere; alle andre
// faller gjennom til det vanlige SSR-løpet urørt.
server.use(botMarkdownMiddleware(browserDistFolder));
server.use(express.static(browserDistFolder, { maxAge: "1y", index: false }));
server.get("*", /* ... Angular CommonEngine-rendering ... */);
Mellomvaren avgjør, per forespørsel, om dette i det hele tatt er en Markdown-forespørsel. Det finnes tre uavhengige utløsere:
- En kjent bot-User-Agent, matchet mot en eksplisitt tillatelsesliste.
- Et eksplisitt
.md-suffiks:/about.mdstrippes til/aboutog serveres som Markdown uavhengig av hvem som spør. Dette er den offentlige, dokumenterbare URL-en. Accept: text/markdown, den standardkompatible veien for enhver klient som velger det.
Hvis ingen av disse slår til, er forespørselen en vanlig sidevisning og faller rett gjennom til SSR. Crawler-løpet når aldri engang rendereren.
Matche boter uten å cloake Google
User-Agent-listen er bevisst smal og utelater bevisst søkecrawlere:
// Kun KI-spesifikke crawlere. Googlebot og Google-Extended er bevisst
// fraværende: \b ordgrenser hindrer delvise treff, så Googles søkecrawler
// fortsetter å motta den fulle HTML-siden. Å servere den nedstrippet
// markdown ville vært cloaking.
const BOT_PATTERN =
/\b(GPTBot|ChatGPT-User|OAI-SearchBot|ClaudeBot|Claude-SearchBot|claude-web|anthropic-ai|PerplexityBot|CCBot|Bytespider|Applebot|Meta-ExternalAgent|FacebookBot|Amazonbot)\b/i;
Dette er linjen det er verdt å være forsiktig med. «Server bot-er annet innhold enn
brukere» er selve definisjonen på cloaking, og søkemotorer straffer det. Forsvaret er at
dette ikke er annet innhold, det er en annen representasjon av samme innhold, slik en
print.css eller en RSS-strøm er. For å holde det forsvarbart:
- Googles søkecrawler er utelatt med vilje. Den får samme HTML som et menneske får. Bare generative agenter / KI-agenter (som ber om tekst de kan fordøye, ikke en rendret side) får Markdown.
- Markdown-en genereres fra sidens eget innhold, så substansen stemmer overens.
- Hvis det ikke finnes Markdown for en rute, faller mellomvaren gjennom til SSR i stedet for å servere en stubb. En bot får aldri en dårligere side enn et menneske; i verste fall får den samme side.
Ikke forgift cachen: Vary
Den ene driftsfaren ved innholdsforhandling er caching. Hvis et CDN cacher Markdown-svaret
under den bare URL-en, får det neste mennesket Markdown, eller omvendt. Løsningen er
Vary-headeren, som forteller cacher at svaret avhenger av bestemte forespørselsheadere:
Vary: User-Agent, Accept
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=1800
Sett Vary: User-Agent, Accept på de dynamiske svarene (både HTML og Markdown), men
ikke på ekte statiske ressurser: CSS, JS, fonter, bilder. Å nøkle statiske filer på
User-Agent ville knust cachen i én oppføring per nettlesertekststreng helt uten gevinst. Så
mellomvaren lar statiske ressurser passere urørt og annoterer kun de forhandlede rutene.
Hvorfor dette er billigere, helt konkret
Under SSR betyr det å produsere én HTML-side å bootstrappe rammeverket, kjøre komponenttreet, hente hva enn data siden trenger, serialisere DOM-en og legge til hydreringstilstand. Det er CPU-bundet og det er den enkeltvis dyreste tingen serveren din gjør per forespørsel.
Markdown-grenen hopper over alt dette. Den leser en cachet streng (eller kjører en lett HTML-til-Markdown-konvertering av allerede hentet innhold) og skriver noen få kilobyte. Grovt sett:
- Ingen rammeverk-bootstrap, ingen rendringstre, ingen serialisering på bot-løpet.
- En brøkdel av bytene over ledningen: ingen markup, skript eller stiler.
- Cachbart for alle bak samme UA/Accept-nøkkel, med kort TTL.
Nyttelasten en crawler fordøyer er også bedre for den: ren prosa med overskrifter og lister, ingen navigasjonskrom eller standardtekst å vasse gjennom. Du bruker mindre og modellen får et renere signal.
Kvote-utbyttet
Her er delen som endrer hvordan du drifter nettstedet. KI-crawlere er aggressive: GPTBot,
ClaudeBot, PerplexityBot og venner kan treffe et nettsted hardt, og den vanlige defensive
refleksen er å strupe dem: rategrenser, Crawl-delay, full blokkering når de topper.
Hvert eneste av disse tiltakene bytter bort dekning. En strupet crawler indekserer mindre av
deg, og i en verden der sitering i KI-svar avhenger av å ha blitt fordøyd i utgangspunktet,
er struping selvdestruktivt.
Innholdsforhandling snur byttehandelen. Når en crawlers forespørsler nesten ikke koster deg noe å servere, trenger du ikke lenger å strupe for å beskytte origin. Du kan:
- Heve eller fjerne rategrenser for gjenkjente KI-agenter, fordi trafikken deres ikke lenger konkurrerer med menneskelig SSR-trafikk om CPU.
- La dem crawle dypere og oftere: ferskere innhold i modellene som siterer deg.
- Slutte å behandle crawler-topper som en hendelse, fordi en topp av Markdown-forespørsler er en avrundingsfeil ved siden av en topp av rendringer.
Du gjør et motsetningsfylt forhold (boter som et lastproblem som skal holdes unna) om til
et samarbeidende: billig å servere, sjenerøst crawlet, godt representert i svarene som i
økende grad sitter mellom innholdet ditt og dets publikum. Det er den samme GEO-logikken som
ligger bak å sende med Markdown-følgesvenner og en llms.txt-indeks i det hele tatt;
innholdsforhandling er bare det som gjør det driftsmessig gratis under SSR.
Når du trenger det, og når du ikke gjør det
Hvis nettstedet ditt er statisk prerendret, trenger du kanskje ikke mellomvaren i det hele
tatt: send med .md-filene som statiske artefakter og la CDN-en servere dem gratis.
Forhandlingslaget gjør seg fortjent nettopp når rendring er dyrt og per forespørsel, og
det er der det å gi botene en billig representasjon slutter å være en bekvemmelighet og begynner
å betale for seg selv i CPU du ikke brenner og rategrenser du ikke trenger å håndheve.