Blog/Web-Engineering
Caching eines Headless-Commerce-Frontends in Nuxt: Route Rules, ISR und Purge
So cachen Sie ein Headless-Commerce-Frontend in Nuxt: Route Rules, Nitro- und CDN-Schlüssel, ISR-Grenzen, Tag-Purge und Kundenpreise außerhalb geteilter Caches.
Balázs Csorba··11 Min. Lesezeit
- Nuxt
- Caching
- ISR
- Headless commerce
- Cache invalidation

Das Wichtigste in Kürze
- Wählen Sie den Caching-Modus je Routenklasse, nicht für die ganze Seite: Prerendern, was sich nie ändert, stale-while-revalidate für Katalogdaten, und Warenkorb, Kasse und Konto bleiben ungecacht.
- Der Name isr bedeutet nicht überall Edge-Caching; die Nuxt-Dokumentation begrenzt den CDN-Teil auf Plattformen, die ihn unterstützen, derzeit Netlify und Vercel. Prüfen Sie daher die Header, die Sie tatsächlich erhalten.
- Nitro bietet auf Ursprungsebene kein Tag-Purge. Taggen Sie Antworten beim Schreiben, purgen Sie per Tag im CDN über einen signierten Webhook und behalten Sie ein kurzes max-age als Rückfallebene.
- Cachen Sie die Hülle einer kundenspezifischen Seite und laden Sie Preise über einen ungecachten, privaten Endpunkt. Kundendaten dürfen nie in einen geteilten Cache gelangen; testen Sie das mit zwei Konten.
- Messen Sie die Trefferquote an den Antwort-Headern jeder Schicht und verfolgen Sie das Alter des ältesten gecachten Eintrags, denn ein Veraltungsbudget, das niemand prüft, ist kein Budget.
Das Caching eines Headless-Commerce-Frontends in Nuxt ist vor allem eine Frage, welche Seiten geteilt werden dürfen. Katalogseiten, Kategorielisten und Marketingseiten lassen sich am Edge für Minuten, Stunden oder bis zum nächsten Deployment cachen, weil jeder Besucher dieselben Bytes sieht. Eine Produktseite für einen angemeldeten B2B-Kunden lässt sich so nicht behandeln, denn Preis, bestellbare Menge und Zustand des Warenkorb-Buttons hängen davon ab, wer fragt. Die Mechanik ist nicht schwierig. Die Fehler entstehen, wenn jede Route denselben Cache-Modus erhält.
Die Arbeitsregel ist kurz: Wählen Sie einen Modus je Routenklasse, halten Sie personalisierte Daten aus allem Geteilten heraus, legen Sie im Voraus fest, wie veraltet jede Klasse sein darf, und klären Sie, wie eine Katalogänderung den Cache erreicht. Nuxt-Route-Rules legen den Modus pro Pfad fest. Nitro entscheidet, wie der Server die Antwort cacht. Das CDN entscheidet, was geteilt wird und wie Purges funktionieren. Jede Schicht hat eigene Standardwerte, einen eigenen Header-Wortschatz und eigene Grenzen, und Stand Oktober 2026 unterscheiden sie sich zwischen Vercel, Cloudflare und einem einfachen Node-Server. Die folgenden Abschnitte gehen diese Schichten durch, dann die Purge-Wege, die Grenze der Personalisierung und die Messung.
Welcher Caching-Modus passt zu welcher Routenklasse?
Prerendern Sie, was sich nie ändert, nutzen Sie stale-while-revalidate für Katalogdaten, die eine kurze Verzögerung vertragen, und halten Sie Warenkorb, Kasse und Konto ungecacht und serverseitig gerendert. Die Tabelle ist ein Ausgangspunkt. Die Veraltungsbudgets sind geschäftliche Entscheidungen, die Sie je Klasse abstimmen sollten, und keine Framework-Standardwerte.
| Routenklasse | Modus | Zwischengespeichert in | Veraltungsbudget | Personalisierung | Hebel für Invalidierung |
|---|---|---|---|---|---|
| Marketing- und Rechtsseiten | prerender | Build-Ausgabe, CDN | bis zum nächsten Deployment | keine | neu deployen |
| Kategorielisten | swr oder isr | Server und CDN | je Klasse abgestimmt | keine | Tag-Purge bei Katalogänderung |
| Produktdetail-Hülle | swr, Client-Preisabruf | nur HTML-Hülle | je Klasse abgestimmt | keine im HTML | Tag-Purge plus max-age |
| Kundenpreise und Bestand | Client-Insel, ungecachte API | nirgends geteilt | keine, bei jeder Anfrage gelesen | pro Kunde | nicht zutreffend |
| Warenkorb, Kasse, Konto | ssr, no-store | nirgends geteilt | keine | pro Kunde | nicht zutreffend |
Der Pfad für kundenspezifische Daten ist überhaupt kein Cache-Modus. Er bleibt by design aus allen geteilten Caches heraus, und die Seitenstruktur sollte diese Grenze im Code sichtbar machen. Das Diagramm zeigt den Anfragepfad für eine gecachte Produktseite.
Was tun die Nuxt-Route-Rules tatsächlich?
Route Rules ordnen einem Pfadmuster Render- und Caching-Verhalten zu. Die Nuxt-4-Dokumentation führt unter anderem redirect, proxy, ssr, cors, headers, swr, isr, prerender und noScripts auf, und Nuxt umhüllt passende Routen mit den Cache-Handlern, die Nitro bereitstellt. Nuxt 4.3 hat die Payload-Extraktion für ISR- und SWR-Routen ergänzt, daher werden die _payload.json-Dateien dieser Routen ebenfalls erzeugt und können neben dem HTML gecacht werden.
Die drei Modi unterscheiden sich in einem wichtigen Punkt. swr ergänzt Cache-Header und cacht die Antwort auf dem Server oder einem Reverse Proxy für eine TTL, danach wird sie nach Ablauf im Hintergrund neu erzeugt. isr verhält sich genauso, mit einer Ergänzung: Die Antwort kann zusätzlich in den CDN-Cache wandern, doch die Nuxt-Dokumentation beschränkt das auf Plattformen, die es unterstützen, und nennt dafür Netlify oder Vercel. prerender erzeugt das HTML zur Build-Zeit und behält es bis zum nächsten Deployment.
Die Folge: isr ist kein übertragbarer Name für Edge-Caching. Auf Vercel steht dahinter das plattformeigene ISR. Auf einem Node-Server liefert dieselbe Regel stale-while-revalidate auf Ursprungsebene, und die CDN-Seite muss man getrennt aufbauen. Schreiben Sie die Regel mit Blick auf die Plattform und prüfen Sie die Antwort-Header im Deployment, statt dem Namen zu vertrauen.
// nuxt.config.ts (sketch)
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/categories/**': { swr: 600 }, // shared catalogue data, revalidated in the background
'/products/**': { swr: 300 }, // shell only; prices come from the client island
'/cart/**': { ssr: true, headers: { 'cache-control': 'private, no-store' } },
'/account/**': { ssr: true, headers: { 'cache-control': 'private, no-store' } },
},
})Wie cacht Nitro eine Antwort?
Der Cache von Nitro basiert auf ocache und nimmt austauschbaren Speicher auf. Route Rules mit einer Cache-Option werden automatisch in defineCachedHandler gehüllt, und swr: true ist eine Kurzform für cache: { swr: true, maxAge }. Zwei Standardwerte sind wichtig. maxAge ist standardmäßig eine Sekunde, ein Eintrag ohne ausdrückliches Alter ist also nahezu wertlos, und der Guide verlangt, es für alles zu setzen, was gecacht werden soll. Außerdem schreibt swr: true ohne staleMaxAge keine TTL in den Speicher. Die Doku nennt das das ISR-Muster, das Wachstum des Caches ist also durch das Speicher-Backend begrenzt und nicht durch die Zeit.
Nitro erzeugt außerdem die Cache-Header selbst. Ein maxAge von 60 ergibt max-age=60. Mit swr kommt s-maxage=60 hinzu, und mit staleMaxAge: 600 kommt stale-while-revalidate=600 dazu. Mit sendCacheControl: false bleibt der Eintrag auf dem Server, ohne dass Browser und CDNs davon erfahren. Das ist nützlich, wenn der Ursprung eine Antwort cachen soll, die die geteilten Schichten nicht speichern sollen.
Die Schlüssel sind standardmäßig konservativ. Query-Parameter und Cookies erreichen den Handler nicht und verändern den Schlüssel nicht, daher teilen sich /page?a=1 und /page?utm=x einen Eintrag. allowQuery und allowCookies schalten das wieder ein. Die Doku warnt, dass allowCookies Vary: Cookie ausgibt, was die Trefferquoten in CDNs und geteilten Proxys zusammenbrechen lassen kann, und empfiehlt, die Auswahl wenn möglich über allowQuery in die URL zu verlagern. set-cookie wird vor dem Cachen entfernt, sodass ein Sitzungscookie nicht über den Cache zu einem anderen Nutzer gelangen kann. Eine Antwort, deren eigenes Vary einen Header außerhalb des Schlüssels nennt, wird ausgeliefert, aber nicht gespeichert. Auch die Sprache ist eine Schlüsseldimension: Jede Sprachvariante braucht einen eigenen Eintrag, und die hreflang-Links dürfen nur auf Varianten zeigen, die tatsächlich ausgeliefert werden können, wie in mehrsprachiges Nuxt-SEO mit hreflang beschrieben.
Der Ursprungs-Cache ist nur die erste Schicht, und die Standardwerte des CDN betreffen nicht HTML. Der Zone-Cache von Cloudflare cacht HTML und JSON standardmäßig nicht; er richtet sich nach der Dateiendung, und hat eine Antwort keinen Cache-Control- oder Expires-Header, gelten Standard-TTLs je Statuscode. Vercel ISR ist in allen Tarifen verfügbar und behält gecachte Inhalte, bis sie neu validiert werden oder 31 Tage lang nicht abgerufen wurden. Cloudflare Workers Cache ist eine neuere, vom Worker verwaltete Option. Ihr Schlüssel enthält den Entrypoint, Pfad und Query-String, die Worker-Version und ctx.props, aber nicht Host oder HTTP-Methode. Sie wird vollständig über Cache-Control- und CDN-Cache-Control-Header gesteuert, umgeht den Cache automatisch bei Set-Cookie und Authorization und speichert nur GET und HEAD. Varianten werden pro exaktem Header-Wert gespeichert, Accept-Encoding: gzip, br und br, gzip sind also getrennte Einträge, und Vary: * deaktiviert das Caching.
Wie erreichen Katalogänderungen den Cache?
Taggen Sie Antworten beim Schreiben, purgen Sie per Tag über einen Webhook und behalten Sie ein kurzes max-age als Rückfallebene. Nitro bietet auf Ursprungsebene kein tagbasiertes Purge, daher leben die Tags im CDN. Bei Cloudflare kann eine Antwort mit einem Cache-Tag-Header per Tag aus dem Dashboard oder über die API gepurgt werden. Tags sind nicht case-sensitiv und dürfen keine Leerzeichen enthalten. Der Gesamt-Header ist auf 16 KB begrenzt, grob tausend eindeutige Tags, und ein API-Tag darf bis zu 1.024 Zeichen lang sein. Ein Purge im Dashboard nimmt bis zu 100 Tags an, und die API begrenzt jede Anfrage auf 100 Operationen, bei Enterprise auf 500. Nach einem Tag-Purge liefert die nächste Anfrage CF-Cache-Status: MISS, hinter Tiered Cache EXPIRED, und die Seite wird vom Ursprung neu gefüllt.
// Purge the CDN cache for every product whose PIM record changed in this batch
const res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ tags: changedSkus.map((sku) => `sku:${sku}`) }), // split into chunks of 100
})
if (!res.ok) throw new Error(`purge failed: ${res.status}`) // fall back to the max age, then alertDer Webhook braucht dieselbe Sorgfalt wie jeder Schreibpfad: Signatur prüfen, Ereignisse deduplizieren und Schübe zusammenfassen, denn ein Massenimport kann in einer Minute Tausende Änderungen auslösen. Purge-Aufrufe sind selbst ein Rate-Limit-Risiko. Purges für Workers Cache nutzen immer die Rate-Limits des Free-Tarifs, daher sollte ein vom Worker verwalteter Cache, der häufig purgt, sie bündeln.
Tags sind der präzise Hebel, die günstige Alternative ist ein kurzes max-age mit stale-while-revalidate. RFC 5861 definiert stale-while-revalidate als Erweiterung, mit der ein Cache eine veraltete Antwort für eine festgelegte Anzahl Sekunden ausliefern darf, während sie im Hintergrund neu validiert wird. Vercel ISR folgt diesem Verhalten, purgt HTML und Datenpayloads atomar über alle CDN-Regionen und liefert nach einer fehlgeschlagenen Revalidierung weiter veralteten Inhalt mit einer TTL von 30 Sekunden aus. Der Preis ist, dass ein Kunde kurz den vorherigen Wert sehen kann; die Länge des Fensters ist daher eine geschäftliche, keine technische Entscheidung.
Wie cacht man Seiten mit Kundenpreisen?
Cachen Sie die Hülle, nicht den Preis. Das HTML einer Produktseite ist für alle gleich und darf daher geteilt werden. Preis, Vertragsrabatt und Zustand des Warenkorb-Buttons kommen von einer Client-Insel, die einen Endpunkt aufruft, der nie geteilt wird, etwa GET /api/price?sku=…&customer=… mit Cache-Control: private. Ein konfiguriertes Produkt fügt eine weitere Schicht hinzu, da die Optionen Preis und Bestellbarkeit verändern; Headless-Produktkonfiguratoren im B2B behandeln diesen Fall, und dieselbe Grenze gilt dort.
Fällt die Preis-API aus, braucht die Seite einen definierten Rückfall, und die Reihenfolge der Präferenzen zählt. Zeigen Sie den Listenpreis mit einem Hinweis zur Anmeldung für den Vertragspreis, niemals einen Kundenpreis aus dem Cache eines anderen Kunden. Liefern Sie als Rückfall kein kundenspezifisches Fragment aus einem geteilten Cache aus. Deaktivieren Sie den Warenkorb-Button, wenn der Bestand nicht bestätigt werden kann, und protokollieren Sie den eingeschränkten Zustand, damit er in den Trefferquoten und Fehlerzahlen erscheint.
Das ist in der EU auch eine Compliance-Frage. Ein geteilter Cache, der die Seite eines Kunden einem anderen ausliefert, ist eine Offenlegung personenbezogener Daten, und die DSGVO behandelt das über den Grundsatz der Integrität und Vertraulichkeit in Artikel 5 Absatz 1 Buchstabe f und die Sicherheit der Verarbeitung in Artikel 32. Führen Sie vor dem Livegang und nach jeder Änderung an den Route Rules einen Leak-Test mit zwei Testkonten im Deployment durch, eines davon mit einer anderen Preisliste. In mehr als zehn Jahren Arbeit mit Vue, Nuxt, TypeScript, Node und PHP, überwiegend bei B2B-E-Commerce- und PIM-Plattformen wie Spryker und Pimcore, kommt genau diese Frage am häufigsten auf: Welche Routen dürfen gecacht werden, wenn jeder Preis vom angemeldeten Kunden abhängt?
Wo sollte man stale-while-revalidate nicht verwenden?
Niemals für Kassen-Gates, für die Verfügbarkeit im Moment des Kaufs oder für Werte, die beim Lesen verbindlich sind. Stale-while-revalidate ist sicher für Daten, bei denen ein kurz falscher Wert akzeptabel ist und sich selbst korrigiert, etwa Kategorielisten, Beschreibungen und Bilder. Commerce-Teams nutzen diese Voreinstellungen bereits, und die dokumentierten Caching-Strategien von Hydrogen sind eine gute Referenz: eine kurze Strategie mit 10 Sekunden, eine lange mit einer Stunde frisch plus einem Stale-Fenster, sodass insgesamt ein Tag entsteht, und no-store für Daten, die nie wiederverwendet werden dürfen. Die Standard-Strategie für Sub-Requests ist max-age=1 mit einem Stale-Fenster von 86.399 Sekunden.
RFC 5861 definiert den Mechanismus, nicht die Richtlinie. Er erschien 2010 und führte stale-while-revalidate und stale-if-error als Cache-Erweiterungen ein; ob ein bestimmter Cache sie beachtet, entscheidet der Cache selbst. Das Stale-Fenster ist daher ein Versprechen an Kunden und sollte neben der Route Rule stehen, die es durchsetzt. Für die Abgrenzung taugt diese grobe Einteilung:
- Kasse und Warenkorb: immer
no-store. - Bestand im Kaufmoment: live lesen, oder bei der Bestellanlage einen geprüften Fehler in Kauf nehmen.
- Suche mit Feature-Flags: das Flag in die URL aufnehmen oder das Ergebnis nicht cachen.
- Katalogdaten und Inhalte: stale-while-revalidate mit schriftlich festgelegtem Budget.
Wie misst man Treffer, und was geht meist schief?
Messen Sie an den Headern, nicht an Dashboards, die Sie noch nicht eingerichtet haben. Vercel liefert x-vercel-cache mit Werten wie HIT, MISS, STALE, PRERENDER, REVALIDATED und BYPASS, und die Doku merkt an, dass dynamische Routen und Abrufe aus dem Runtime-Cache oft MISS zeigen, obwohl sie aus dem Runtime-Cache kommen. Cloudflare liefert CF-Cache-Status mit HIT, MISS, BYPASS, DYNAMIC, EXPIRED, REVALIDATED, UPDATING und STALE und setzt Age bei HIT, STALE und UPDATING. Nitro sendet einen eigenen x-cache-Header mit HIT, STALE, REVALIDATED und MISS. Dieselbe Seite kann also zwei Status melden, einen von der Ursprungsschicht und einen vom CDN, und ein Dashboard, das nur einen davon liest, führt in die Irre.
- Caching authentifizierter Antworten. Jede Antwort, die von einem Cookie oder einem
Authorization-Header abhängt, braucht eine ausdrückliche private- oder no-store-Richtlinie. Workers Cache umgeht bei diesen Headern, die Regel, auf die es ankommt, ist aber die, die Sie für jede andere Cache-Schicht schreiben. - Vary-Wildwuchs. Ein
Vary: Cookieoder ein weit gefassterAccept-Header zerteilt die Einträge, bis die Trefferquote einbricht. - Vergiftung über nicht abgedeckte Eingaben. Alles, was die Antwort ändert, ohne den Schlüssel zu ändern, etwa ein Rewrite- oder Override-Header, muss in den Schlüssel aufgenommen oder entfernt werden, bevor es den Cache erreicht.
- Query-Strings und Cookies fehlen im Schlüssel. Nitro lässt beides standardmäßig weg, daher braucht eine Route, die von einem Query-Parameter abhängt,
allowQuery, sonst teilen sich zwei URLs einen Eintrag. - Treffer, die trotzdem Geld kosten. Bei Workers spart ein Cache-Treffer CPU-Zeit, zählt aber trotzdem als Request, die Request-Rechnung sinkt also nicht mit der Trefferquote.
- Purge-Stürme. Purge-Aufrufe sind ratenbegrenzt, und Workers-Cache-Purges nutzen die Limits des Free-Tarifs, daher braucht ein Massenimport Bündelung.
Der letzte Stolperstein trifft Teams nach einem erfolgreichen Launch: veraltete Inhalte, die niemand prüft. Verfolgen Sie das Alter des ältesten gecachten Katalogeintrags zusätzlich zur Trefferquote. Ein Veraltungsbudget hilft nur, wenn etwas meldet, wenn es überschritten wird. Dasselbe gilt für generierte Inhalte. Ich betreibe LLM-Funktionen auf meiner eigenen Nuxt-Seite und habe sie mit einer LLM-Pipeline übersetzt; sobald generierter Text in einer gecachten Seite landet, ist eine veraltete Übersetzung ein Korrektheitsfehler und kein kosmetischer.
Eine Checkliste für den Rollout
Die Reihenfolge zählt. Ein Purge-Weg nützt nichts, wenn die Antworten keine Tags tragen, und eine Trefferquote täuscht, solange die Grenze der Personalisierung unklar ist. Arbeiten Sie die Schritte der Reihe nach ab und behandeln Sie jeden als Tor für den nächsten.
- Ordnen Sie jede Route einer Klasse aus der Tabelle zu und schreiben Sie das Veraltungsbudget daneben.
- Legen Sie die Route Rule für jede Klasse fest und machen Sie das Plattformverhalten von
israusdrücklich. - Fügen Sie jeder Katalogantwort beim Schreiben Tags hinzu, etwa eines je SKU und eines je Kategorie.
- Verbinden Sie das PIM- oder ERP-Veröffentlichungsereignis mit einem signierten Webhook, der Purges nach Tag bündelt.
- Stellen Sie Trefferquote je Schicht und das Alter des ältesten gecachten Eintrags bereit, mit den oben beschriebenen Headern.
- Testen Sie einen Purge unter Spitzenlast und halten Sie fest, wie lange die nächsten Anfragen zum Neufüllen des Caches brauchen.
- Führen Sie einen Leak-Test mit zwei Konten durch, eines mit einer anderen Preisliste, gegen das Deployment.
Wenn Sie ein Commerce-Frontend auf Nuxt bauen oder reparieren und möchten, dass die Caching-Grenze unter Last hält, sehen Sie sich meine Vue- und Nuxt-Arbeit an. Die Renderinggeschwindigkeit ist eine eigene Frage mit eigenen Kennzahlen, behandelt in Nuxt Core Web Vitals.
Quellen
- Nuxt docs: rendering modes and route rules (v4)
- Nuxt blog: Nuxt 4.3 release (ISR and SWR payload extraction)
- Nitro docs: caching
- Cloudflare docs: default cache behavior
- Cloudflare docs: purge by tags
- Cloudflare docs: Workers Cache
- Cloudflare docs: cache responses (CF-Cache-Status)
- Vercel docs: incremental static regeneration
- Vercel docs: response headers (x-vercel-cache)
- Shopify docs: Hydrogen caching strategies
- RFC 5861: HTTP Cache-Control extensions for stale content
- Regulation (EU) 2016/679 (GDPR), EUR-Lex
Häufige Fragen
Was ist der Unterschied zwischen swr und isr in Nuxt-Route-Rules?
Beide ergänzen stale-while-revalidate-Verhalten auf dem Server oder einem Reverse Proxy. Laut Nuxt-Dokumentation kann isr die Antwort zusätzlich im CDN-Cache ablegen, auf Plattformen, die das unterstützen, derzeit Netlify und Vercel. Auf einem einfachen Node-Server greift der CDN-Teil nicht; prüfen Sie die Header in Ihrem Deployment.
Wie purgen Sie eine gecachte Nuxt-Seite, wenn sich ein Produkt ändert?
Nitro bietet auf Ursprungsebene kein Tag-Purge, also taggen Sie die Antwort im CDN mit einem Cache-Tag-Header, etwa einem Tag je SKU, und purgen Sie per Tag über einen Webhook, sobald sich das Produkt ändert. Behalten Sie ein kurzes max-age mit stale-while-revalidate als Rückfallebene, falls ein Purge-Aufruf fehlschlägt.
Kann man Seiten mit B2B-Kundenpreisen cachen?
Ja, wenn der gecachte Teil die gemeinsame Hülle ist und der Preis von einem separaten Endpunkt kommt, der nie geteilt wird. Markieren Sie diesen Endpunkt als private oder no-store und halten Sie kundenspezifisches HTML aus allen geteilten Caches fern, auch aus CDN-Caches und Proxys.
Kostet Vercel ISR etwas?
Laut Vercel ist ISR Stand Oktober 2026 in allen Tarifen verfügbar. Lesen und Schreiben im CDN-Cache sind kostenlos, während Lese- und Schreibzugriffe auf den dauerhaften Speicher regionale Kosten verursachen, die im ISR-Abschnitt des Nutzungsdashboards erscheinen.
Senkt ein Cache-Treffer in Cloudflare Workers die Rechnung?
Nicht die Request-Gebühr. Cloudflare dokumentiert, dass Cache-Treffer als Requests zählen, aber keine CPU-Zeit verbrauchen. Eine höhere Trefferquote spart also CPU, während die Request-Gebühren bei gleichem Traffic gleich bleiben.