Problem Statement
Als Shopbetreiber/Entwickler will ich, dass die Katalog-Seiten (SEO-URL-Auflösung, Kategorie-, Produkt- und Landing-Entitäten, Navigation) vom neu eingeführten Shopware-http_cache bedient werden. Bisher war die Store-API nicht cachebar, weshalb nuxtware einen eigenen Nitro-Cache-Layer (cachedFunction) für SEO-URL und Navigation gebaut hat, während Kategorie- und Produkt-Reads ungecacht per POST an die Store-API gehen.
Mit dem Shopware Cache-Rework (Feature-Flag CACHE_REWORK, Default ab 6.8) sind nicht-mutierende Store-API-Routen per GET mit komprimierter Criteria (?_criteria) cachebar. Im Betrieb wird Varnish entfernt und durch Shopwares eingebauten http_cache ersetzt. Solange nuxtware seine Katalog-Reads aber per POST absetzt und parallel einen eigenen, jetzt redundanten Cache betreibt, greift der neue http_cache überhaupt nicht: kein Performance-Gewinn, doppelter Cache-Code, unnötige Backend-Last.
Solution
Die Katalog-Route-Reads, die nicht an die Shopware-Customer-Session gebunden sind, werden auf ihre cachebaren GET-Varianten umgestellt, damit der Shopware-http_cache sie ausliefert; der redundante Nitro-Cache-Layer für SEO-URL und Navigation wird ersatzlos entfernt.
- Wo die shopware-frontends-Composables die GET-Variante bereits abbilden (SEO-URL, Navigation, Produkt-Detail), genügt das Opt-in-Flag
cacheableReads.
- Wo die SDK die GET-Variante nur wegen einer Typ-Lücke (
_criteria auf GET nicht getippt) noch auf POST belässt, obwohl die Store-API-Route GET beherrscht (Kategorie-Detail, Landing-Detail — verifiziert an CategoryRoute/LandingPageRoute: methods:[GET,POST], _httpCache=>true), wird per offiziellem Composable-Override (Wrap-and-Spread) auf GET umgestellt.
- Die HTTP-Methode wird pro Request nach der Criteria gewählt: GET (cachebar) nur für „cachebare Listing-Queries" (Query-Parameter ausschließlich aus einer Allow-List, initial nur Pagination
p), sonst POST. SSR sendet immer die volle Criteria und rendert immer das vollständige, angeforderte Ergebnis — die Methode beeinflusst nur die Cachebarkeit, nie den gerenderten DOM. Damit bleiben Hydration-Korrektheit und Crawler-/No-JS-Sicht erhalten, während unbounded Filter-Permutationen die Cache-Hit-Rate nicht verwässern.
Siehe ADR docs/adr/0001-store-api-catalog-caching.md und Glossar CONTEXT.md.
User Stories
- Als Shopbetreiber möchte ich, dass anonyme Kategorie-Seitenaufrufe vom Shopware-
http_cache bedient werden, damit die Store-API-Last und die Antwortzeiten sinken.
- Als Shopbetreiber möchte ich, dass anonyme Produkt-Detailseiten vom
http_cache bedient werden, damit häufig aufgerufene Produkte nicht bei jedem Request neu berechnet werden.
- Als Shopbetreiber möchte ich, dass die SEO-URL-Auflösung cachebar ist, damit die teuerste Route (jeder Katalog-Aufruf beginnt damit) nicht ungecacht durchschlägt.
- Als Shopbetreiber möchte ich, dass die Navigations-/Menü-Daten cachebar sind, damit sie nicht pro Request neu geladen werden.
- Als Shopbetreiber möchte ich, dass Landing-Pages cachebar sind, damit Kampagnen-Seiten unter Last stabil bleiben.
- Als Entwickler möchte ich den redundanten Nitro-Cache-Layer (SEO-URL + Navigation) vollständig entfernen, damit es nur noch eine Cache-Autorität pro Ebene gibt und weniger Code zu pflegen ist.
- Als Entwickler möchte ich
cacheableReads in nuxt.config aktivieren, damit die abgedeckten Composables (SEO-URL, Navigation, Produkt-Detail) automatisch auf GET umschalten.
- Als Entwickler möchte ich
useCategorySearch().search per offiziellem Wrap-and-Spread-Override auf die GET-Variante der Kategorie-Detail-Route umstellen, ohne die restliche Composable-API zu übernehmen.
- Als Entwickler möchte ich
useLandingSearch().search analog auf GET überschreiben, weil die Store-API-Route GET beherrscht, auch wenn die Release Notes sie nicht listen.
- Als Entwickler möchte ich, dass der Override die HTTP-Methode anhand der Query-Parameter wählt, damit nur cachebare Anfragen als GET laufen.
- Als Entwickler möchte ich eine zentrale, leicht erweiterbare Allow-List (
CACHEABLE_LISTING_PARAMS, initial {'p'}), damit später weitere cachebare Parameter ohne Logikänderung ergänzt werden können.
- Als Shop-Kunde möchte ich beim Öffnen einer gefilterten/sortierten Kategorie-URL sofort das korrekte, vollständig serverseitig gerenderte Ergebnis sehen, damit es keine Hydration-Sprünge gibt.
- Als Suchmaschinen-Crawler (ohne Browser) möchte ich im SSR-DOM exakt das in der URL angeforderte Katalog-Ergebnis sehen, damit gefilterte/paginierte Views korrekt erfasst werden.
- Als Shop-Kunde möchte ich, dass eine paginierte Kategorie-Seite (
?p=2) cachebar und schnell ist, weil paginierte Views ein bounded, häufig genutztes Muster sind.
- Als Shop-Kunde möchte ich Filter/Sortierung/Limiter interaktiv anwenden können, wobei diese (client-seitigen) Anfragen per POST laufen und nicht gecacht werden müssen.
- Als Entwickler möchte ich, dass das Basis- und paginierte Produkt-Listing über den
cmsPage-Slot der Kategorie-Antwort mit-gecacht wird, ohne einen separaten Listing-Call einzuführen.
- Als Entwickler möchte ich, dass
[...dynamic].vue die SEO-Auflösung über useNavigationSearch().resolvePath() macht und sowohl ein rohes SeoUrl-Element als auch ein bereits aufgelöstes {routeName, foreignKey} verarbeitet.
- Als Entwickler möchte ich, dass die technische-URL-Sonderbehandlung (
/navigation/…, /detail/…, /landingPage/…) erhalten bleibt (durch resolvePath als Superset abgedeckt), damit direkte technische URLs weiter funktionieren.
- Als Entwickler möchte ich, dass die Sprach-/Währungs-/Kontext-Header (
sw-language-id, sw-currency-id, sw-context-hash) im SSR stabil gesendet werden, damit der Cache-Key nicht fragmentiert.
- Als Consumer-Projekt, das die nuxtware-Layer erweitert, möchte ich das Kategorie-/Landing-Override weiter per eigenem Composable-Shadowing anpassen können, weil der offizielle Override-Mechanismus die Layer-Präzedenz respektiert.
- Als Entwickler möchte ich die Deps (composables 1.12, api-client 1.5, nuxt-module 1.5) upgraden und
@shopware/api-gen gegen ein CACHE_REWORK-Backend neu generieren, damit die ...Get-Operationen getippt sind.
- Als Entwickler möchte ich die
_criteria-Typ-Lücke auf den GET-Detail-Routen mit einem schmalen, dokumentierten Cast überbrücken, damit der Typecheck nicht bricht.
- Als Betreiber ohne shared Cache im SSR-Pfad möchte ich verstehen, dass die GET-Umstellung ohne
http_cache wirkungslos ist, damit die Voraussetzung explizit ist.
- Als Entwickler möchte ich die Umstellung so kapseln, dass die Overrides trivial entfernbar sind, sobald shopware-frontends die getippten GET-Detail-Varianten offiziell nachzieht.
Implementation Decisions
-
Zielarchitektur: Browser → Nitro [nuxt-multi-cache] → Shopware [http_cache] → Store-API. Der eigene Nitro-cachedFunction-Layer entfällt; nuxt-multi-cache (SSR-Page-Cache) bleibt unverändert.
-
Dependency-Upgrade: @shopware/composables@1.12, @shopware/api-client@1.5, @shopware/nuxt-module@1.5 (+ passende helpers). cacheableReads: true unter shopware in nuxt.config. Store-API-Typen via @shopware/api-gen neu generieren.
-
Entfernen (redundanter Nitro-Cache): die Nitro-Routen für /seo-url und /navigation, die zugehörigen gecachten Server-Utils, das eigene useNavigationCached-Composable und die apiCacheLifetime-Konfiguration.
-
SEO-URL: Auflösung über useNavigationSearch().resolvePath(); die aufrufende Katalog-Route verarbeitet beide Rückgabeformen (SeoUrl-Element oder {routeName, foreignKey}) und behält das bestehende 404-Verhalten.
-
Navigation: Umstieg von useNavigationCached auf das Framework-useNavigation().
-
Produkt-Detail: useProductSearch().search schaltet über cacheableReads automatisch auf GET; bestehende Associations (withCmsAssociations, manufacturer/media/cover) wandern in _criteria.
-
Kategorie-Detail & Landing-Detail: offizieller Composable-Override (Datei-Shadowing, Wrap-and-Spread) — nur search ersetzt, Rest via ...core. Der Override repliziert das Framework-Muster cacheableReads ? invoke(get, {query:{_criteria}}) : invoke(post, {body}). _criteria wird wegen der SDK-Typ-Lücke schmal gecastet; die intern nicht-exportierte cmsAssociations-Konstante wird nachgebaut.
-
Methoden-Wahl nach Criteria (zentrale Regel): Ein Prädikat isCacheableListingQuery(query) liefert true, wenn alle Query-Keys in CACHEABLE_LISTING_PARAMS (initial new Set(['p'])) enthalten sind. Nur dann GET, sonst POST. Gilt für den Kategorie-Override; Landing-Requests haben typischerweise keine Listing-Params → GET. Ableitung aus der Grill-Session:
keine Query → GET (Basis, cachebar)
?p=2 → GET (bounded: Seite 1..N)
?order=… → POST (korrekt gerendert, nicht gecacht)
?properties=…&min-price → POST
-
Interaktives Listing: Filter/Sorter/Limiter/Pagination-Klicks laufen weiter client-seitig über useCategoryListing().search (POST). Basis- und paginiertes Listing werden über den cmsPage-Slot der Kategorie-Antwort mit-gecacht — kein separater Listing-Call.
-
API-Contract: Alle Katalog-Reads enden in apiClient.invoke(operationString, options); operationString kodiert Operation + Methode + Pfad (z.B. readCategory get /category/{navigationId}), Criteria als query._criteria (GET) bzw. body (POST).
Testing Decisions
- Was ist ein guter Test: nur externes Verhalten prüfen — bei gegebener Eingabe welche Store-API-Operation und -Methode aufgerufen wird — nicht die interne Implementierung. Kein Testen privater Helfer als Selbstzweck.
- Primäre Seam (Vitest + Mocks):
useShopwareContext().apiClient.invoke mocken und asserten, dass die Overrides pro Eingabe die korrekte Operation wählen:
- Kategorie ohne Query →
readCategory get /category/{navigationId} mit query._criteria.
- Kategorie mit
?p=2 → GET.
- Kategorie mit
?order=… / Filter → readCategory post /category/{navigationId} mit body.
- Landing → GET.
- (Mit
cacheableReads: false → durchweg POST, Backwards-Compat.)
Diese eine Seam deckt SEO-URL, Navigation, Produkt, Kategorie und Landing ab.
- Integrationstests: als separate TypeScript-Skripte, außerhalb von Vitest und nicht in CI/CD, gegen ein echtes
CACHE_REWORK-Backend. Prüfen: GET-Kategorie/Landing liefern die render-fertige cmsPage, _criteria wird zur Laufzeit angewendet, und die Antworten tragen die erwarteten Cache-Control-Header.
- Neue Test-Infrastruktur: Vitest (+
@nuxt/test-utils soweit nötig) wird eingeführt — das Repo hat aktuell keine automatisierten Tests; es gibt daher keine Prior Art. Bestehende Quality-Gates (npx nuxt typecheck, npm lint) bleiben ergänzend.
Out of Scope
- Infra-Umstellung selbst (Varnish entfernen, Shopware-
http_cache aktivieren/konfigurieren) — Betriebs-/Deployment-Aufgabe, Voraussetzung dieser Umstellung.
- Caching der interaktiven Produkt-Listing-Filter (Sortierung, Property-/Preis-Filter, geänderter Limiter) — bleiben bewusst POST/ungecacht (unbounded Permutationen). Später via Erweiterung von
CACHEABLE_LISTING_PARAMS möglich.
- Invalidations-Kohärenz zwischen
nuxt-multi-cache (SSR-Seiten) und Shopware-http_cache (Store-API) — angrenzendes Thema, eigener Klärungsbedarf.
- Customer-Session-gebundene Routen (Cart, Account, Checkout) — per Definition nicht cachebar, nicht Teil dieser Umstellung.
Further Notes
- Voraussetzung: ein shared Cache (Shopware-
http_cache) im SSR-Pfad; ohne ihn ist die GET-Umstellung wirkungslos.
- Typ-Lücke: Kategorie-/Landing-Override hängen an fehlenden
_criteria-Typen auf den GET-Detail-Routen — zu entfernen, sobald shopware-frontends die getippten GET-Varianten liefert. Kandidat für ein Upstream-Issue/-PR (deckt sich mit dem Roadmap-Punkt „Create GH Issue for question about cache rework").
- Roadmap: entspricht
docs/roadmap.md → Performance → „Cache rework implementation / Catalog Requests will be converted from POST to GET".
- Backwards-Compat:
cacheableReads ist per Default aus; Projekte ohne http_cache können auf POST bleiben (die Overrides fallen dann automatisch auf POST zurück).
Problem Statement
Als Shopbetreiber/Entwickler will ich, dass die Katalog-Seiten (SEO-URL-Auflösung, Kategorie-, Produkt- und Landing-Entitäten, Navigation) vom neu eingeführten Shopware-
http_cachebedient werden. Bisher war die Store-API nicht cachebar, weshalb nuxtware einen eigenen Nitro-Cache-Layer (cachedFunction) für SEO-URL und Navigation gebaut hat, während Kategorie- und Produkt-Reads ungecacht per POST an die Store-API gehen.Mit dem Shopware Cache-Rework (Feature-Flag
CACHE_REWORK, Default ab 6.8) sind nicht-mutierende Store-API-Routen per GET mit komprimierter Criteria (?_criteria) cachebar. Im Betrieb wird Varnish entfernt und durch Shopwares eingebautenhttp_cacheersetzt. Solange nuxtware seine Katalog-Reads aber per POST absetzt und parallel einen eigenen, jetzt redundanten Cache betreibt, greift der neuehttp_cacheüberhaupt nicht: kein Performance-Gewinn, doppelter Cache-Code, unnötige Backend-Last.Solution
Die Katalog-Route-Reads, die nicht an die Shopware-Customer-Session gebunden sind, werden auf ihre cachebaren GET-Varianten umgestellt, damit der Shopware-
http_cachesie ausliefert; der redundante Nitro-Cache-Layer für SEO-URL und Navigation wird ersatzlos entfernt.cacheableReads._criteriaauf GET nicht getippt) noch auf POST belässt, obwohl die Store-API-Route GET beherrscht (Kategorie-Detail, Landing-Detail — verifiziert anCategoryRoute/LandingPageRoute:methods:[GET,POST],_httpCache=>true), wird per offiziellem Composable-Override (Wrap-and-Spread) auf GET umgestellt.p), sonst POST. SSR sendet immer die volle Criteria und rendert immer das vollständige, angeforderte Ergebnis — die Methode beeinflusst nur die Cachebarkeit, nie den gerenderten DOM. Damit bleiben Hydration-Korrektheit und Crawler-/No-JS-Sicht erhalten, während unbounded Filter-Permutationen die Cache-Hit-Rate nicht verwässern.Siehe ADR
docs/adr/0001-store-api-catalog-caching.mdund GlossarCONTEXT.md.User Stories
http_cachebedient werden, damit die Store-API-Last und die Antwortzeiten sinken.http_cachebedient werden, damit häufig aufgerufene Produkte nicht bei jedem Request neu berechnet werden.cacheableReadsinnuxt.configaktivieren, damit die abgedeckten Composables (SEO-URL, Navigation, Produkt-Detail) automatisch auf GET umschalten.useCategorySearch().searchper offiziellem Wrap-and-Spread-Override auf die GET-Variante der Kategorie-Detail-Route umstellen, ohne die restliche Composable-API zu übernehmen.useLandingSearch().searchanalog auf GET überschreiben, weil die Store-API-Route GET beherrscht, auch wenn die Release Notes sie nicht listen.CACHEABLE_LISTING_PARAMS, initial{'p'}), damit später weitere cachebare Parameter ohne Logikänderung ergänzt werden können.?p=2) cachebar und schnell ist, weil paginierte Views ein bounded, häufig genutztes Muster sind.cmsPage-Slot der Kategorie-Antwort mit-gecacht wird, ohne einen separaten Listing-Call einzuführen.[...dynamic].vuedie SEO-Auflösung überuseNavigationSearch().resolvePath()macht und sowohl ein rohesSeoUrl-Element als auch ein bereits aufgelöstes{routeName, foreignKey}verarbeitet./navigation/…,/detail/…,/landingPage/…) erhalten bleibt (durchresolvePathals Superset abgedeckt), damit direkte technische URLs weiter funktionieren.sw-language-id,sw-currency-id,sw-context-hash) im SSR stabil gesendet werden, damit der Cache-Key nicht fragmentiert.@shopware/api-gengegen einCACHE_REWORK-Backend neu generieren, damit die...Get-Operationen getippt sind._criteria-Typ-Lücke auf den GET-Detail-Routen mit einem schmalen, dokumentierten Cast überbrücken, damit der Typecheck nicht bricht.http_cachewirkungslos ist, damit die Voraussetzung explizit ist.Implementation Decisions
Zielarchitektur:
Browser → Nitro [nuxt-multi-cache] → Shopware [http_cache] → Store-API. Der eigene Nitro-cachedFunction-Layer entfällt;nuxt-multi-cache(SSR-Page-Cache) bleibt unverändert.Dependency-Upgrade:
@shopware/composables@1.12,@shopware/api-client@1.5,@shopware/nuxt-module@1.5(+ passendehelpers).cacheableReads: trueuntershopwareinnuxt.config. Store-API-Typen via@shopware/api-genneu generieren.Entfernen (redundanter Nitro-Cache): die Nitro-Routen für
/seo-urlund/navigation, die zugehörigen gecachten Server-Utils, das eigeneuseNavigationCached-Composable und dieapiCacheLifetime-Konfiguration.SEO-URL: Auflösung über
useNavigationSearch().resolvePath(); die aufrufende Katalog-Route verarbeitet beide Rückgabeformen (SeoUrl-Element oder{routeName, foreignKey}) und behält das bestehende 404-Verhalten.Navigation: Umstieg von
useNavigationCachedauf das Framework-useNavigation().Produkt-Detail:
useProductSearch().searchschaltet übercacheableReadsautomatisch auf GET; bestehende Associations (withCmsAssociations, manufacturer/media/cover) wandern in_criteria.Kategorie-Detail & Landing-Detail: offizieller Composable-Override (Datei-Shadowing, Wrap-and-Spread) — nur
searchersetzt, Rest via...core. Der Override repliziert das Framework-MustercacheableReads ? invoke(get, {query:{_criteria}}) : invoke(post, {body})._criteriawird wegen der SDK-Typ-Lücke schmal gecastet; die intern nicht-exportiertecmsAssociations-Konstante wird nachgebaut.Methoden-Wahl nach Criteria (zentrale Regel): Ein Prädikat
isCacheableListingQuery(query)lieferttrue, wenn alle Query-Keys inCACHEABLE_LISTING_PARAMS(initialnew Set(['p'])) enthalten sind. Nur dann GET, sonst POST. Gilt für den Kategorie-Override; Landing-Requests haben typischerweise keine Listing-Params → GET. Ableitung aus der Grill-Session:Interaktives Listing: Filter/Sorter/Limiter/Pagination-Klicks laufen weiter client-seitig über
useCategoryListing().search(POST). Basis- und paginiertes Listing werden über dencmsPage-Slot der Kategorie-Antwort mit-gecacht — kein separater Listing-Call.API-Contract: Alle Katalog-Reads enden in
apiClient.invoke(operationString, options);operationStringkodiert Operation + Methode + Pfad (z.B.readCategory get /category/{navigationId}), Criteria alsquery._criteria(GET) bzw.body(POST).Testing Decisions
useShopwareContext().apiClient.invokemocken und asserten, dass die Overrides pro Eingabe die korrekte Operation wählen:readCategory get /category/{navigationId}mitquery._criteria.?p=2→ GET.?order=…/ Filter →readCategory post /category/{navigationId}mitbody.cacheableReads: false→ durchweg POST, Backwards-Compat.)Diese eine Seam deckt SEO-URL, Navigation, Produkt, Kategorie und Landing ab.
CACHE_REWORK-Backend. Prüfen: GET-Kategorie/Landing liefern die render-fertigecmsPage,_criteriawird zur Laufzeit angewendet, und die Antworten tragen die erwartetenCache-Control-Header.@nuxt/test-utilssoweit nötig) wird eingeführt — das Repo hat aktuell keine automatisierten Tests; es gibt daher keine Prior Art. Bestehende Quality-Gates (npx nuxt typecheck,npm lint) bleiben ergänzend.Out of Scope
http_cacheaktivieren/konfigurieren) — Betriebs-/Deployment-Aufgabe, Voraussetzung dieser Umstellung.CACHEABLE_LISTING_PARAMSmöglich.nuxt-multi-cache(SSR-Seiten) und Shopware-http_cache(Store-API) — angrenzendes Thema, eigener Klärungsbedarf.Further Notes
http_cache) im SSR-Pfad; ohne ihn ist die GET-Umstellung wirkungslos._criteria-Typen auf den GET-Detail-Routen — zu entfernen, sobald shopware-frontends die getippten GET-Varianten liefert. Kandidat für ein Upstream-Issue/-PR (deckt sich mit dem Roadmap-Punkt „Create GH Issue for question about cache rework").docs/roadmap.md→ Performance → „Cache rework implementation / Catalog Requests will be converted from POST to GET".cacheableReadsist per Default aus; Projekte ohnehttp_cachekönnen auf POST bleiben (die Overrides fallen dann automatisch auf POST zurück).