# 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:

```ts
// 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:

```ts
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:

1. **En kjent bot-User-Agent**, matchet mot en eksplisitt tillatelsesliste.
2. **Et eksplisitt `.md`-suffiks**: `/about.md` strippes til `/about` og serveres som
   Markdown uavhengig av hvem som spør. Dette er den offentlige, dokumenterbare URL-en.
3. **`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:

```ts
// 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.
