
# Warum Ihr CDN nichts cacht: der Header, der es verhindert

Jede statische Datei von agentready.md kam mit derselben Zeile zurück:

```
cf-cache-status: BYPASS
```

Schriften, Stylesheets, Skripte, Bilder. Kein `MISS` — ein MISS hieße, das Edge hat die Datei noch nicht gesehen und behält die Kopie, die es gerade geholt hat. `BYPASS` heißt, das Edge hat die Antwort gelesen, entschieden, dass es sie nicht speichern darf, und entscheidet morgen genauso. Jeder Besucher zog jedes Byte vom Origin, und hätte das für immer weiter getan.

## Die vier Einstellungen, die nicht schuld waren

- **Development Mode** — aus. Er schaltet den Edge-Cache drei Stunden lang komplett ab und bleibt gern versehentlich an, also zuerst prüfen.
- **Caching Level** — Standard.
- **Page Rules** — keine setzte Cache Level auf Bypass.
- **Browser Cache TTL** — ein vernünftiger Wert.

Alles, was das Dashboard zum Thema Cache zu sagen hatte, sagte *cache das*. Das Edge cachte nichts.

## Die Antwort stand in der Antwort

Sie zeigt sich, sobald man den ganzen Header-Block liest statt der einen Zeile, wegen der man gekommen ist:

```bash
curl -sI https://agentready.md/fonts/ibm-plex-sans-latin.woff2
```

```
HTTP/2 200
content-type: font/woff2
cache-control: public, max-age=31536000, immutable
set-cookie: _csrf=8f3c…; Path=/; SameSite=Strict
cf-cache-status: BYPASS
```

Zeile drei bittet um ein Jahr. Zeile vier erklärt, warum es niemand gewährt.

**Eine Antwort mit `Set-Cookie` ist in einem gemeinsamen Cache nicht speicherbar.** Ein Cookie gehört einem Besucher; wer diese Antwort speichert und dem Nächsten ausliefert, gibt ihm das Cookie eines Fremden. Jedes CDN verweigert das. Cloudflare gibt der Verweigerung den Namen `BYPASS`, andere speichern das Objekt einfach nicht und sagen nichts.

Das Cookie war unseres. Eine Middleware erzeugte bei jeder Anfrage ein CSRF-Token und hängte es an, damit jedes irgendwo gerenderte Formular eines vorfindet. Zu „jeder Anfrage" gehörte die nach einer woff2-Schrift — einer Datei, die kein Formular rendert, kein Token liest und keinen Code ausführt.

## Die Lösung

Der CSRF-Schutz bleibt, wie er ist. Es bekommt nur nichts mehr ein Token, was damit nichts anfangen kann:

```js
const ASSET_PREFIXES = ['/css/', '/js/', '/fonts/', '/images/'];
const COOKIELESS_FILES = new Set([
  '/favicon.ico', '/badge.js', '/robots.txt', '/sitemap.xml', '/llms.txt',
]);

function servesWithoutCookie(path) {
  return COOKIELESS_FILES.has(path)
    || ASSET_PREFIXES.some((prefix) => path.startsWith(prefix))
    || path.endsWith('.md');
}
```

Vergleichen Sie Pfade, keine Dateiendungen. Manche der Antworten, die am dringendsten in den Cache gehören, werden generiert statt von der Platte gelesen: der Markdown-Zwilling jeder Seite, unsere SVG-Badges. Ein Formular rendern die auch nicht.

Wenn das Cookie nicht von Ihnen stammt, sehen Sie sich die Session-Schicht an. Viele Frameworks öffnen eine Session und schicken ihr Cookie bei jeder Anfrage, die durch die Middleware läuft, auch wenn nie etwas darin gespeichert wurde.

## Die Diagnose, auf jedem Stack

Prüfen Sie ein Asset, nicht die Startseite. Eine Startseite ist oft aus guten Gründen nicht cachebar, und über die dreißig Dateien, die sie nachlädt, sagt sie nichts.

```bash
for u in / /css/app.css /js/app.js /fonts/ibm-plex-sans-latin.woff2; do
  echo "== $u"
  curl -sI "https://beispiel.de$u" \
    | grep -iE 'cache-control|set-cookie|cf-cache-status|x-cache|^age'
done
```

Fragen Sie dieselbe URL zweimal ab. Der zweite Aufruf sollte ein Treffer sein: `cf-cache-status: HIT`, `x-cache: Hit from cloudfront` oder ein `age:`-Header, der bei Varnish und nginx hochzählt. Ein zweiter Miss heißt, das Objekt wird nicht behalten, und der Grund steht in den Headern, die Sie gerade ausgegeben haben.

## Die anderen üblichen Verdächtigen

`Cache-Control: private` — nur Browser, nie ein gemeinsamer Cache. `no-store` — niemand, nicht einmal der Browser. `no-cache` bedeutet nicht „nicht cachen", sondern vor jeder Wiederverwendung revalidieren, und das ist weiterhin ein Roundtrip pro Datei.

`Vary: *` macht eine Antwort schlicht uncachebar. `Vary: Cookie` ist fast so schlimm: der Cache-Key enthält dann einen Wert, der sich pro Besucher unterscheidet, also wird nie etwas geteilt. `Vary: User-Agent` zersplittert eine Datei in Tausende Varianten. `Accept-Encoding` ist der, der dort hingehört.

Query-Strings. Die meisten Caches nehmen die ganze URL als Schlüssel, also ist jede `?utm_source=`- und `?fbclid=`-Variante ein eigenes Objekt, das vom Origin geholt wird. Kampagnenparameter am Edge entfernen oder normalisieren.

Und der Framework-Standard, der kein Bypass ist, aber fast genauso teuer: `max-age=0` auf jeder statischen Datei bedeutet eine Revalidierungsanfrage pro Asset und pro Seitenaufruf.

## Eine Cache-Policy, die trägt

Die Regel richtet sich danach, was die URL verspricht, nicht danach, was die Datei ist.

Eine URL mit Content-Hash (`app.css?v=9f2ac41b`, `app.9f2ac41b.css`) ist eine Inhaltsadresse: ändert sich die Datei, ändert sich die URL. Die bekommen `public, max-age=31536000, immutable`, und es gibt nie etwas zu purgen. Dieselbe Datei ohne Token bekommt eine Stunde: das Token ist das Versprechen, und auf ein Versprechen, das niemand gegeben hat, wird nichts gecacht. Ein Logo oder ein Social-Cover, das an Ort und Stelle ersetzt wird, bekommt eine Woche.

Dann die Ausnahme, die man übersieht, weil die Datei nach dem cachebarsten Ding aussieht, das man besitzt: alles, was in fremden Seiten eingebettet ist, unter einer URL, die Sie nicht versionieren können. Wir liefern `badge.js` genau deshalb mit einer Stunde aus. Es läuft in Sites, wo wir weder das HTML noch die URL kontrollieren — ein Jahr `immutable` wäre ein Bug, an den wir ein Jahr lang nicht herankommen.

## Wenn Sie Dateien mit @fastify/static ausliefern

Zwei Details, die uns einen Nachmittag gekostet haben. Das Modul sendet standardmäßig `Cache-Control: public, max-age=0`. Und wenn Sie den Header selbst über `setHeaders` setzen, geben Sie auch `cacheControl: false` mit, sonst baut die Bibliothek aus ihrem Standard-`maxAge` einen eigenen und überschreibt Ihren — kommentarlos, und was stehen bleibt, sieht absichtlich aus:

```js
app.register(fastifyStatic, {
  root: join(__dirname, 'public'),
  cacheControl: false,           // sonst wird setHeaders überschrieben
  setHeaders: setStaticCacheHeaders,
});
```

Bevor Sie eine einzige Cache-Einstellung anfassen, lesen Sie eine Antwort vollständig. Der Header, der das Caching kaputt macht, ist selten der mit „cache" im Namen.

[Site prüfen](/de) · [Checkliste für KI-Bereitschaft](/de/tools/ai-readiness-checklist) · [Sind die Bots zugelassen?](/de/tools/ai-crawlers-checker)
