Page Caching
Page caching stores the fully rendered HTML of pages so subsequent (usually anonymous) visitors are served a prebuilt response instead of executing PHP, hitting the database, and running plugins. Done right, it slashes TTFB, CPU usage, and origin queries while remaining invisible to users.
Where page caching lives (layers)
- Application/server cache: Nginx FastCGI cache, Varnish, or Apache modules store HTML near PHP-FPM.
- CDN edge cache: Edges cache HTML for anonymous traffic (respecting cookies/headers).
- Browser cache: Keeps assets locally; not true “page” cache, but complementary.
How it works
- First request renders the page and stores the HTML using a cache key (e.g., host + path + selected query params + device/country).
- Later requests with the same key return the cached HTML instantly.
- Bypass rules prevent caching for logged-in users, carts, checkouts, admin, or requests that set dynamic cookies.
- Invalidation (purge) occurs on deploy/content/product updates or after TTL expiry.
- Revalidation can use
ETag
/Last-Modified
to serve304 Not Modified
efficiently.
What to cache vs. bypass
- Cache: Home, posts/pages, product/category archives, landing pages (anonymous).
- Bypass:
/wp-admin/*
,/wp-login.php
, account pages, cart/checkout, pages varying by user/session. - Conditional: Product pages with dynamic pricing or stock—cache but purge on change or vary by critical params.
Cache keys & vary rules
- Keep keys small and stable: host + path; whitelist only meaningful query strings (e.g.,
page
,utm_*
usually excluded). - Avoid
Vary: Cookie
; instead, bypass when specific cookies exist. - If you localize/AB-test, consider a separate key dimension (e.g.,
Accept-Language
, experiment ID) or move logic to the client.
Invalidation strategies
- Event-driven purge: On publish/update of posts/products, purge their URLs + related archives.
- Tag-based purge: Assign surrogate keys (e.g.,
post-123
,term-45
) and purge by tag on updates. - TTL-based: Reasonable HTML TTLs (e.g., 5–15 minutes) with stale-while-revalidate for smooth refreshes.
- Full purge: Only for breaking changes; it can hammer the origin.
WordPress / WooCommerce specifics
- Bypass on cookies:
wordpress_logged_in_*
,woocommerce_items_in_cart
,woocommerce_cart_hash
,wp_woocommerce_session_*
. - Purge hooks: On product price/stock change, order status affecting availability, or category updates, purge product URL + category pages.
- Fragmentation (ESI/hole-punch): For dynamic widgets (mini-cart, “recently viewed”), render via AJAX or edge includes so the page stays cached.
- Search & filters: Cache base listing pages, but let filter/sort URLs be short-TTL or bypassed if highly personalized.
- Multilingual: Include language path/subdomain in the cache key; purge per locale.
Example configs (simplified)
Nginx FastCGI (HTML microcaching)
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($http_cookie ~* "wordpress_logged_in_|woocommerce_items_in_cart|wp_woocommerce_session_") { set $skip_cache 1; }
location ~ \.php$ {
fastcgi_no_cache $skip_cache;
fastcgi_cache_bypass $skip_cache;
fastcgi_cache WORDPRESS;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache $upstream_cache_status;
}
Apache header hints for HTML (paired with CDN)
<FilesMatch "\.html?$">
Header set Cache-Control "public, s-maxage=600, max-age=0, stale-while-revalidate=60, stale-if-error=3600"
</FilesMatch>
Best practices
- Prefer edge + server cache together; shield the origin.
- Use conservative HTML TTLs with
stale-while-revalidate
/stale-if-error
. - Instrument cache hit ratio and TTFB; watch for spikes after deploys (indicative of missed purges).
- Normalize/strip tracking params from the cache key.
- Keep a warmup list for critical URLs post-deploy.
Common pitfalls
- Caching personalized pages (users see others’ carts or prices).
- Forgetting to purge category archives when a product changes.
- Excessive
Vary
(e.g., on all cookies), exploding cache cardinality. - Serving mixed content or non-HTTPS variants from cache.
- Relying on plugin-level cache alone without aligning CDN rules.
KPIs
- Cache Hit Ratio (CHR) for HTML; target >70% for anonymous traffic.
- TTFB at edge vs. origin (aim for large delta).
- Origin request rate/CPU (should drop notably).
- Purge latency and error rate during revalidation.