Een caching plugin die geïnstalleerd en actief is, betekent niet dat pagina's uit de cache worden geserveerd. Het betekent dat de plugin dat kán doen. Of het ook echt gebeurt, hangt ervan af of het verzoek matcht met een van de bypass-regels die elke serieuze caching plugin meelevert, en op een WooCommerce-site is het antwoord meestal "ja, bijna elk verzoek matcht een bypass-regel, en geen ervan is overduidelijk". Dit artikel is het diagnosepad: hoe je ziet of een pagina überhaupt gecached wordt, en zo niet, waarom niet.
Voor het onderliggende model van wat elke cachelaag doet, is het concept-artikel over de cachelagen je achtergrondlectuur. Dit artikel gaat ervan uit dat je al een page cache plugin hebt draaien en wilt weten waarom die niks lijkt te doen.
Wordt de pagina eigenlijk wel gecached
Voordat je een oorzaak gaat diagnosticeren, moet je eerst vaststellen dát er een miss is om te diagnosticeren. Open een anonieme browser (niet een incognito tab van je ingelogde profiel, een aparte browser of curl). Vraag een pagina waarvan je zeker weet dat-ie cachebaar is twee keer op. Het tweede verzoek zou een cache hit moeten zijn.
De check zit in de response headers. Open de DevTools van je browser (F12 of rechtermuisknop, Inspecteren), ga naar het tabblad Network en herlaad de pagina. Klik op het paginaverzoek (het eerste HTML-document in de lijst) en zoek in de Response Headers naar een header met de naam x-cache, cf-cache-status, age, x-proxy-cache of x-nginx-cache. WP Rocket, LSCache, Varnish, nginx fastcgi_cache en Cloudflare noemen de header allemaal net anders, maar de waarde is hetzelfde: HIT betekent dat de pagina uit cache is geserveerd, MISS betekent dat dat niet is gebeurd.
Herlaad de pagina nog een keer en check opnieuw. Het tweede verzoek zou HIT moeten tonen.
Als je SSH-toegang hebt, werkt dezelfde check ook op de command line:
curl -sI https://jouwsite.nl/voorbeeld-pagina/ | grep -Ei '^(x-cache|cf-cache-status|age|x-proxy-cache|x-nginx-cache)'
Verwachte output bij een cache hit:
x-cache: HIT
age: 42
Zie je op geen enkel verzoek een HIT, dan is de plugin wel geïnstalleerd maar serveert hij niks uit cache. Dat is precies het scenario waar dit artikel over gaat. Is het eerste verzoek een miss en het tweede een hit, dan werkt de plugin prima en heb je eigenlijk een ander probleem: de cache expiry staat te kort, of er is een specifieke groep URL's die altijd mist.
De meest voorkomende oorzaak op WooCommerce-sites: sessie cookies die op elke pagina lekken
Dit is veruit de belangrijkste reden waarom een WooCommerce-site een site-brede cache hit rate van bijna nul heeft, en het is ook de oorzaak die niemand doorheeft omdat het dashboard van de caching plugin geen foutmelding geeft.
Het mechanisme. Elke serieuze caching-laag omzeilt de page cache zodra hij een WooCommerce sessie cookie in het verzoek ziet. Dat is correct gedrag voor cart en checkout, waar de cookie betekent "deze bezoeker heeft een eigen winkelwagen". Het probleem is dat WooCommerce die cookies op elke pagina zet waar woocommerce.php wordt geladen, dus niet alleen op cart en checkout. Iemand opent de homepage, wp_woocommerce_session_<hash> wordt gezet, hij navigeert naar een blogpost, de caching-laag ziet de cookie op dat verzoek, en de blogpost wordt ongecached geserveerd.
Dit staat expliciet in WooCommerce's eigen documentatie over caching plugins: de cookies woocommerce_cart_hash, woocommerce_items_in_cart en wp_woocommerce_session_* moeten op dynamische pagina's een bypass triggeren. De WooCommerce-docs waarschuwen er niet expliciet voor dat die cookies ook op niet-dynamische pagina's lekken, maar in de praktijk gebeurt het wel, en dát is wat de hit rate kapot maakt.
Hoe je checkt of dit jouw probleem is.
- Open je site in een verse anonieme browser. Leg niks in het winkelmandje.
- Bezoek de homepage, daarna een productpagina, daarna een blogpost.
- Open DevTools, ga naar Application en dan Cookies, en kijk of
wp_woocommerce_session_<hash>erbij staat. Zie je hem, dan wordt de cookie gezet op een pagina die helemaal niks met de cart te maken heeft. - Vraag nu een normaal cachebare pagina op (een blogpost, een Over-ons-pagina) en check de cache headers zoals hierboven. Was die pagina eerder een
HITen nu eenMISS, dan is de cookie de oorzaak.
De fix hangt af van je caching plugin.
- WP Rocket en LiteSpeed Cache handelen dit standaard al netjes af: hun bypass-regels triggeren alleen op cart-, checkout- en my-account-URL's, niet op de aanwezigheid van de cookie zelf. Zit je op een van deze twee en heb je toch het probleem, dan is er bijna altijd ooit een custom regel toegevoegd in de trant van "bypass cache op WooCommerce cookies", en die regel is te breed. Haal hem eruit.
- WP Super Cache bypasst de cache zodra een bekende "bad cookie" aanwezig is, inclusief de WooCommerce sessie cookie. De fix is om in
wp-config.phpof in hetwp-cache-config.php-bestand ervoor te zorgen datwp_woocommerce_session_níet in de$wp_cache_rejected_cookies-lijst staat als je die pagina's gecached wilt hebben. Of, nog beter, gebruik server-level bypass-regels (nginx of Varnish) die de bypass strikt beperken tot cart- en checkout-URL's. - W3 Total Cache heeft een "Rejected Cookies"-veld onder Page Cache, Advanced. Dezelfde regel: haal
wp_woocommerce_session_uit die lijst en de page cache gaat pagina's weer serveren ook als de cookie er is. Cart en checkout worden nog steeds correct overgeslagen, omdat WooCommerce daar deDONOTCACHEPAGE-constante rechtstreeks op zet. - Server-level page cache (nginx fastcgi_cache, Varnish): de bypass-logica zit in de serverconfiguratie, niet in de plugin. Kijk naar de
fastcgi_cache_bypass-directive of naar je Varnish VCL en zorg dat de cookie-bypass alleen op cart en checkout triggert, niet globaal.
Je weet dat het werkt als de cache hit rate op blogposts en niet-cart-pagina's weer omhoog klimt, terwijl cart, checkout en my-account nog steeds netjes X-Cache: MISS laten zien (en dat is voor die drie pagina's ook de bedoeling).
Recente relevante wijziging. WooCommerce 10.3 introduceerde een experimentele feature die lege sessie cookies bij clients opruimt, zodat een bezoeker die niks in z'n winkelwagen heeft gelegd de wp_woocommerce_session_*-cookie niet meer meesleept. Omdat het als experimenteel is uitgebracht moet je het expliciet aanzetten en kan het gedrag in latere releases nog veranderen. Op shops die 10.3 draaien met de feature actief is het cookielekkage-probleem voor browse-only-verkeer dus een stuk kleiner.
Ingelogde gebruikers worden niet gecached, en dat is geen bug
Dit is de een-na-meest-voorkomende klacht en tegelijk ook de makkelijkste om op te lossen, want het juiste antwoord is "de cache werkt gewoon en je test hem verkeerd".
Het mechanisme. Elke grote page cache plugin (WP Rocket, WP Super Cache, W3TC, LiteSpeed Cache) sluit ingelogde gebruikers standaard uit van de page cache. Een ingelogde gebruiker krijgt een persoonlijke admin bar, user-specifieke nonces en content die van z'n rol afhangt. Een gedeelde cached pagina aan een ingelogde gebruiker serveren zou andermans state in z'n browser lekken, dus stapt de plugin opzij en bouwt elke keer een verse pagina.
WP Rocket zegt het letterlijk: "Pages as a logged-in user shouldn't be cached, unless you enable User Cache, log out of WordPress or visit the site using an incognito window" (bron).
Hoe je checkt of dit jouw probleem is. Log uit bij WordPress. Open een anonieme browser (niet je ingelogde profiel). Vraag dezelfde pagina twee keer op en check de response headers. Is het tweede verzoek een HIT terwijl je uitgelogd bent en een MISS als je ingelogd bent, dan werkt de plugin precies zoals het hoort.
De fix. Niet fixen. De bypass klopt. Is performance voor ingelogde gebruikers écht een probleem (redacteuren die klagen dat wp-admin traag is, leden van een ledensite die tijdens het browsen traagheid melden), dan is page caching niet het antwoord. Het juiste antwoord is:
- Een persistente object cache (Redis of Memcached) zodat de database queries achter de paginabouw snel zijn. Het concept-artikel legt uit wat object caching doet en hoe het verschilt van page caching.
- Uitzoeken wat er aan de admin-kant nou precies traag is. Het artikel over een trage WordPress admin behandelt Heartbeat, autoload-bloat en de plugin-admin-gate, en dat is meestal de echte oorzaak.
- WP Rocket's User Cache feature, die per gebruiker een cachebestand aanmaakt. Dat is alleen zinnig op sites met een handvol ingelogde gebruikers. Op een ledensite met tienduizend leden krijg je gewoon tienduizend cachebestanden en dat is geen verbetering ten opzichte van een Redis erop zetten.
Je weet welk probleem je echt hebt als je in een anonieme browser test en een HIT ziet. Werkt dat, dan is de page cache prima in orde en kijk je naar een andere vraag ("ingelogde gebruikers voelen de site traag"), en daar was page caching nooit het antwoord op.
UTM-parameters en query string fragmentatie
Deze is plugin-afhankelijk, en de korte samenvatting op de homepage van elke plugin verbergt echt verschillend gedrag. Het resultaat is voorspelbaar: tijdens een mail- of advertentiecampagne zakt de cache hit rate richting nul, omdat elke klik een unieke query string meesleept en elke unieke URL z'n eigen cache-entry krijgt.
Het mechanisme. Een URL met een query string is standaard een ándere URL voor caching-doeleinden. jouwsite.nl/product/blauw-shirt/ en jouwsite.nl/product/blauw-shirt/?utm_source=email zijn twee aparte pagina's. Slaat de cache ze allebei op, dan krijgt de eerste bezoeker uit de mail een miss (de cache bouwt de pagina op) en alle daaropvolgende bezoekers uit dezelfde mailing een hit tegen de UTM-variant. Slaat de cache query-string-varianten helemaal niet op, dan is elke mailklik een miss.
Het cruciale detail: plugins doen dit verschillend, en het standaardgedrag is niet wat de meeste mensen denken.
- WP Rocket stript bekende tracking-parameters automatisch. De documentatie noemt de ondersteunde parameters (
utm_source,utm_medium,utm_campaign,utm_term,utm_content,gclid,fbcliden nog een paar platform-specifieke codes). Een URL met alleen deze parameters krijgt hetzelfde cachebestand als de basis-URL. - WP Rocket edge case. Bevat een URL een mix van genegeerde én niet-genegeerde parameters, dan serveert WP Rocket hem niet uit cache.
?utm_source=email&country=frwordt een miss, wantcountrystaat niet in de ignore-lijst. Dat is gedocumenteerd gedrag, geen bug, en een veelvoorkomende oorzaak van lage hit rates op sites die campaign-tracking combineren met geolocatie- of personalisatie-parameters. Zelfde bron. - WP Super Cache en W3 Total Cache negeren UTM-parameters standaard niet.
?utm_source=emaillevert dus gewoon een aparte cache-entry op. Op een site die tijdens een campagne voor het eerst een van deze plugins gebruikt, kan de hit rate zomaar naar bijna nul zakken zolang de campagne loopt. - LiteSpeed Cache stript UTM-parameters standaard wel via de "Drop Query String"-instelling, maar check dat in de plugin onder Cache, Excludes.
- WordPress VIP filtert op platformniveau ook geen UTM-parameters. De VIP-docs geven een lijst van wat er automatisch wél wordt gefilterd, en UTM staat daar niet bij.
Hoe je checkt of dit jouw probleem is. Open een verse anonieme browser. Vraag jouwsite.nl/voorbeeld-pagina/ twee keer op en bevestig dat het tweede verzoek een cache hit is. Vraag nu jouwsite.nl/voorbeeld-pagina/?utm_source=test&utm_medium=email op en check de cache header. Zakt de hit rate op de UTM-variant, dan stript je plugin de parameters niet.
De fix.
- WP Super Cache: Onder Advanced voeg je
utm_source,utm_medium,utm_campaign,utm_term,utm_content,gclid,fbclidtoe aan "Accepted Filenames & Rejected URIs", of gebruik de Extra-tab van de plugin om query string-handling expliciet te configureren. WP Super Cache's gedocumenteerde aanpak is om bepaalde parameters expliciet als cache-equivalent te markeren. - W3 Total Cache: Onder Page Cache, Advanced kun je in het "Accepted query strings"-veld parameters opgeven die uit de cache key gestript moeten worden. Zet je UTM-parameters daar neer.
- WP Rocket: regelt de gangbare gevallen al. Zie je toch misses, kijk dan of je analytics-setup niet een niet-standaard parameter meegeeft. Voeg die toe aan de ignore-lijst als dat passend is.
- Server-level caches (Varnish, nginx): strip de UTM-parameters in de cache-key-generatie. Een gangbare nginx-oplossing gebruikt
mapom$argste saneren voordat de sleutel wordt opgebouwd.
Je weet dat het werkt als een URL met alleen UTM-parameters bij het tweede verzoek dezelfde cache hit geeft als de basis-URL.
Cart, checkout en my-account moeten uitgesloten worden, en alleen die drie
De keerzijde van het cookieprobleem is de overcorrectie. Een supportmedewerker van de hosting of een plugin-wizard zegt tegen de eigenaar "WooCommerce is niet cache-vriendelijk, zet caching op de hele shop uit", en de eigenaar sluit het sitebreed uit of excludeert elke pagina waar het woord "shop" in voorkomt. Dat is de verkeerde fix.
Het mechanisme. WooCommerce definieert zelf precies drie pagina's die niet gecached mogen worden: cart, checkout en my-account. De caching-documentatie van WooCommerce is daar expliciet over: die drie pagina's "need to stay dynamic since they display information specific to the current customer and their cart." Elke andere WooCommerce-pagina (het shoparchief, productpagina's, categoriepagina's, tagpagina's) is prima cachebaar en hoort gecached te worden. Productpagina's zijn nou net waar het meeste verkeer op een succesvolle shop terechtkomt, en die ongecached laten "voor de zekerheid" kost je bakken geld.
WooCommerce zet automatisch de DONOTCACHEPAGE-constante op cart, checkout en my-account. Die constante wordt ondersteund door WP Super Cache, W3 Total Cache en WP Rocket, wat betekent dat op een goed geconfigureerde site die drie pagina's de cache uit zichzelf overslaan, zonder dat je er ook maar één handmatige regel voor hoeft toe te voegen. Sinds minstens WooCommerce 1.4.2 wordt de constante automatisch gezet op die drie dynamische pagina's.
Hoe je checkt of dit jouw probleem is. Zoek in de exclusion-lijst van de caching plugin naar regels die méér afvangen dan alleen /cart, /checkout en /my-account. Gangbare over-excludes:
*shop*dat het shoparchief uitsluit, terwijl dat cachebaar is/product/*dat alle productpagina's uitsluit, terwijl die juist cachebaar zijn*woocommerce*dat alles met dat woord in de URL afvangt/category/*dat categoriepagina's excludeert op sites waar de WooCommerce-categorie prefix overlapt met een blogcategorie prefix
De fix. Haal elke exclude weg die niet cart, checkout of my-account is. Productpagina's, categoriepagina's en shoparchieven horen in de cache. Check het door een productpagina twee keer anoniem op te vragen; het tweede verzoek moet een HIT zijn. Gebruik je nog een extra front-end-endpoint (bijvoorbeeld een custom endpoint voor bezorging of een builder-preview), sluit dan alleen dat specifieke endpoint uit, niet de shop. Sluit ook het WooCommerce AJAX-endpoint /?wc-ajax=* uit, dat in WooCommerce 2.4 in juli 2015 werd geïntroduceerd als vervanger van admin-ajax.php, en dat door cart fragments en een aantal frontend-interacties wordt gebruikt. Dat is geen pagina om te cachen.
Je weet dat het werkt als product- en categoriepagina's bij het tweede anonieme verzoek X-Cache: HIT teruggeven, terwijl cart, checkout en my-account nog steeds MISS laten zien.
Cart fragments die op elke pagina vuren
Deze is ouder en op moderne WooCommerce minder gangbaar, maar hij is nog wel het nachecken waard op shops die niet recent geüpdatet hebben.
Het mechanisme. Het cart fragments script van WooCommerce doet een AJAX-call naar ?wc-ajax=get_refreshed_fragments om de cart widget synchroon te houden met de serverstate. Vóór WooCommerce 7.8 in 2023 werd dat script standaard op elke front-end-pagina enqueued, dus elke pageview triggerde een ongecached PHP-verzoek. De pagina zelf kon nog wel gecached zijn, maar de AJAX-call landde op PHP en draaide bij elke pageload de complete WooCommerce-stack door.
WooCommerce 7.8 heeft de default veranderd, zodat het cart fragments script alleen nog laadt wanneer (a) er daadwerkelijk een cart widget op de pagina staat, (b) een third-party script hem als dependency opgeeft, of (c) hij expliciet enqueued wordt. Zelfde bron. Op moderne WooCommerce met een default theme is de extra PHP-hit dus weg op pagina's die geen cart widget tonen.
Hoe je checkt of dit jouw probleem is. Open DevTools, tabblad Network, en filter op wc-ajax. Laad een blogpost of een willekeurige niet-shop-pagina. Zie je een get_refreshed_fragments-verzoek staan, dan vuurt het cart fragments script nog steeds op elke pagina.
De fix. Draai je WooCommerce 7.8 of nieuwer, haal dan de cart widget weg van niet-shop-pagina's, of controleer of een theme of plugin het script niet opnieuw enqueued. Op oudere WooCommerce of in themes die gepatcht zijn zodat de fragments toch blijven vuren, zet je in de functions.php van je theme een snippetje neer om hem te dequeuen:
// Cart fragments script dequeuen buiten shop-, cart- en checkout-pagina's.
add_action( 'wp_enqueue_scripts', function () {
if ( is_shop() || is_cart() || is_checkout() || is_product() ) {
return;
}
wp_dequeue_script( 'wc-cart-fragments' );
}, 11 );
Je weet dat het werkt als het verzoek wc-ajax=get_refreshed_fragments niet meer opduikt in het network-tabblad op een blogpost of Over-ons-pagina.
De cache hit ratio meten, niet alleen "staat-ie aan"
Zodra je één pagina als cache hit hebt bevestigd, is de volgende vraag of de hit rate op het geheel van de site ook hoog genoeg is. Een plugin die op de homepage werkt maar op 80% van de long tail mist, geeft precies hetzelfde trage-site-symptoom als helemaal geen cache.
Waar je moet kijken, hangt af van wat je draait:
- WP Rocket: geen eigen hit-rate-dashboard. Gebruik je server access logs.
- WP Super Cache: de Cache Contents-pagina van de plugin laat het aantal cachebestanden zien, niet de hit/miss-ratio.
- W3 Total Cache: het Dashboard-tabblad heeft een live request counter met cache hits en misses zodra de "statistics"-feature is aangezet.
- LiteSpeed Cache: draait LSCache op een LiteSpeed server, dan toont het Dashboard van de plugin crawler- en cache-stats.
- nginx fastcgi_cache: de cache hit rate is terug te vinden in het access log als je
$upstream_cache_statusaan je log format toevoegt:
log_format cache_log '$remote_addr - $upstream_cache_status [$time_local] '
'"$request" $status $body_bytes_sent';
access_log /var/log/nginx/access.log cache_log;
Dan verschijnt de cache-status (HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING, REVALIDATED) op elke requestregel en kun je tellen:
awk '{print $4}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
Een gezonde WooCommerce-contentmix zit meestal ergens tussen 70% en 90% HIT op het volledige log. Zit je onder de 50%, dan is er werk aan de winkel, en zit je tegen de nul aan, dan is het bijna altijd een van de oorzaken uit dit artikel.
- Cloudflare: het Caching analytics-dashboard toont de cache hit ratio aan de edge, los van de origin-cache. Hoge edge-hit-rate en lage origin-hit-rate betekent dat Cloudflare het werk doet en de origin-plugin grotendeels overbodig is; lage edge-hit-rate betekent dat de edge het meeste verkeer doorstuurt naar de origin, en dan moet je kijken naar Cloudflare page rules en naar de cache headers die je site teruggeeft.
Wanneer je moet escaleren
Als de checks in dit artikel geen oorzaak blootleggen, verzamel dan het volgende vóórdat je hulp inschakelt. Dit is wat een hosting- of performance-engineer op het eerste bericht nodig heeft:
- De response headers van
https://jouwsite.nl/voorbeeld-pagina/twee keer geladen (gebruik het Network-tabblad in DevTools, ofcurl -sIals je SSH-toegang hebt), met de headers van het eerste én tweede verzoek - De exacte caching plugin-naam en -versie (Plugins, Geïnstalleerd, versienummer kopiëren)
- WooCommerce-versie (indien van toepassing) en of HPOS aanstaat (Instellingen, Geavanceerd, Features)
- WordPress- en PHP-versies (Gereedschap, Site Status, Info, Server)
- De lijst van cookies die worden gezet bij een verse anonieme bezoek aan de homepage (DevTools, Application, Cookies)
- De response headers van een URL mét UTM-parameters en dezelfde URL zónder
- Of er een CDN voor de site staat en welke (Cloudflare, Fastly, Bunny)
- De exclusion-lijst van de plugin en eventuele custom bypass-regels
- De server-level cache-configuratie (nginx fastcgi_cache-directives of Varnish VCL) als je een eigen server draait
De eerste vraag van je hostingprovider is ergens een subset van deze lijst. Dit vooraf klaarleggen scheelt dagen heen-en-weer.
Hoe je herhaling voorkomt
- Loop na elke plugin-installatie of grote update je cache-exclusion-lijst even na. Veel plugins voegen stilletjes eigen excludes toe en halen ze bij deactivatie niet weg.
- Check de hit rate opnieuw na elke marketingcampagne-launch. UTM-verkeer legt query string-fragmentatie direct bloot.
- Houd WooCommerce up to date, zodat de sessie cookie-verbeteringen (10.3+) en de cart-fragment-changes (7.8+) actief zijn.
- Stapel geen twee page cache plugins. Maar één ervan kan
wp-content/advanced-cache.phpbezitten. Het caching concept-artikel legt uit waarom dat structureel misgaat. - Draai je een shop met serieus verkeer, zet dan een persistente object cache neer. Page caching slaat werk over. Object caching maakt het werk sneller zodra het wél moet draaien, en dat is precies wat een ingelogde of ongecachte pagina nodig heeft. Het artikel over een trage WooCommerce legt uit waarom het niet-cachebare oppervlak van een shop object caching onevenredig waardevol maakt.