Caching
Cache layers from edge to application, what's active vs supported, and how to invalidate.
Caching happens at multiple layers between the visitor and the database. Understanding which layer is serving stale content is the first step in debugging cache issues.
Cache Layers
Cloudflare Edge Cache
Cloudflare caches public pages at the edge based on two rules configured in the Cloudflare dashboard:
| Rule | Match | Action |
|---|---|---|
| Bypasses | URI starts with /.well-known or /healthz | Skip cache |
| Cache public pages | URI does not start with /wp/wp-admin and cookie does not contain wordpress_logged_in | Eligible for cache with edge TTL |
Logged-in users always bypass the edge cache. Health check and ACME challenge endpoints are never cached.
Invalidate: Cloudflare dashboard → Caching → Purge Cache, or via API.
Static Assets (Nginx + Browser)
Nginx serves static assets (JS, CSS, images, fonts, SVGs) directly without hitting PHP, with expires max headers. Browsers cache these indefinitely. Cache busting is handled by content-hash query strings (e.g., app.css?ver=c3f97dd56f5e46be03ea) — when assets change, the hash changes and browsers fetch the new version.
Redis Object Cache (Supported, Not on Heroku)
The yax-performance mu-plugin supports Redis as a WordPress object cache backend. When active, it caches database query results, option lookups, and other expensive operations in memory. Runs in local dev via Docker. Not available on Heroku without a paid add-on.
Invalidate (local dev):
docker compose exec redis redis-cli FLUSHALL
npm run wp -- cache flushWordPress Transients
The provider data handler caches AJAX collection responses as transients to mitigate the N+1 query pattern. Without Redis, transients fall back to the wp_options table — still faster than re-running queries.
Invalidate:
npm run wp -- transient delete --allWhat's Cached Where
| Content | Layer | TTL | Notes |
|---|---|---|---|
| Static assets (JS, CSS, fonts, images) | Nginx + Browser | Indefinite | Cache-busted by content hash |
| Public HTML pages | Cloudflare Edge | Edge TTL | Bypassed for logged-in users |
Health check (/healthz) | None | — | Always bypasses all caches |
| wp-admin pages | None | — | Always bypasses edge cache |
| Provider AJAX data | WP Transients | Application-controlled | Falls back to MySQL without Redis |
| Database queries | Redis (when available) | Per-request | Not active on Heroku |
Debugging Stale Content
- CSS/JS looks old — Check that
npm run productionwas run andassets/dist/was committed. The content hash in the URL should change. - Page content is stale for visitors — Purge Cloudflare cache. Check the
wordpress_logged_incookie bypass is working. - Page content is stale for you (logged in) — Cloudflare shouldn't be caching. Check Redis (local dev) or WP transients.
- Provider data is stale — Flush transients:
npm run wp -- transient delete --all - Everything looks stale after a deploy — Flush Redis (if available) and purge Cloudflare.