Ahosting Logo
Knowledge Base

Why WooCommerce Carts and Sessions Break with Caching

Which parts of a store may be cachedCacheable· product pages· category listings· the shop front page· ordinary content pagesNever cacheable· cart· checkout· account pages· anything showing a specific person their own dataWhat happens if you cache them anywayA customer is served somebody else cart, or somebody else address at checkout. It is almostalways caching, and it is a data exposure.

A customer reports that their cart contains items they did not add, or that it empties when they navigate. Occasionally something worse: they see a name that is not theirs at checkout.

This is nearly always caching, and it is not a WooCommerce fault. A cache stores one copy of a page and serves it to everyone, which is exactly right for a product page and exactly wrong for a cart.

What must never be cached

/cart/, /checkout/, /my-account/, and anything else that displays something belonging to one visitor.

Most caching plugins exclude these by default when they detect WooCommerce. The failures come from setups where that detection did not happen: a server-level cache, a CDN configured separately, or a plugin installed before WooCommerce was.

What should be cached

Product pages, category listings, the shop page, ordinary content. These are identical for every visitor and they are the bulk of the traffic.

This is why the answer is exclusions rather than disabling caching. A store with caching off is slow on precisely the pages that decide whether someone reaches the cart at all. See WooCommerce speed optimisation.

The cart total in the header

Most themes show a cart count in the header, which appears on every page: including the cached ones.

WooCommerce handles this with cart fragments: the page is cached, and a small request afterwards fills in the personal part.

Two things go wrong. If the fragment request is itself cached, everyone gets the same count. The alarming symptom. And on a slow server, the fragment request delays every page load, which is why some optimisation guides suggest disabling fragments entirely.

Disabling them is reasonable only if the header count is removed too. Leaving a count that never updates is worse than not showing one.

Check what is happening

curl -I https://example.com/cart/ | grep -i -E 'cache|age|x-cache'

A cache header indicating a hit on the cart URL is the problem, stated plainly.

Test the same way a customer experiences it: add an item, then load the cart in a private window. A cart with contents in a session that never added anything is conclusive.

Layers, and finding which one

There are usually several: a plugin, possibly a server cache, possibly a CDN, plus the browser.

Each must be told separately. Excluding the cart in the plugin achieves nothing if a CDN in front is caching the same page, and this is the common configuration where everything looks correctly set and the fault persists.

Work outside in. Clear the CDN first and retest; if the fault survives, the CDN was not it. Understanding WordPress caching layers goes over identifying them.

Sessions and logged-in customers

WooCommerce stores cart contents in a session, keyed by a cookie. Caching that ignores cookies serves a logged-in customer the version built for a logged-out visitor.

Any caching layer must be configured to bypass for the WooCommerce and WordPress session cookies. A cart that empties on navigation, or a customer who appears logged out on some pages and in on others, is this.

Object caching is different

Page caching stores finished pages; object caching stores database query results. Object caching does not produce this class of fault and is genuinely valuable for a store, because WooCommerce queries heavily.

Keep them distinct when reading advice. "Disable caching to fix the cart" nearly always refers to page caching, and applying it to object caching removes a real benefit for no reason.

Test as a customer before launch

The reliable check is a full purchase in a private window, on a store with caching enabled exactly as it will run.

Add items, change quantities, reach checkout, complete an order. Then repeat in a second private window and confirm the second session sees an empty cart.

Nearly every cart-caching fault would have been caught by those two minutes. Checkout optimisation deals with what else that walk-through reveals.

Prove the exclusions are working

Configuring exclusions is easy and confirming them is the step that gets skipped, which is why stores go live with a cached basket.

for p in / /shop/ /cart/ /checkout/ /my-account/; do
  printf '%-14s %s\n' "$p" "$(curl -sI "https://example.com$p" | grep -iE 'x-cache|cf-cache-status' | tr -d '\r' | head -1)"
done

The first two should be cached. The last three must not be. Any hit on a basket, checkout or account page is a fault regardless of how the plugin reports its configuration.

Test with a session cookie as well, since some caches behave differently once a visitor has one. A page that is correctly excluded for a logged in customer and cached for an anonymous one with items in their basket is the most common version of this.

The fragment that has to stay live

The basket count in the header is the reason stores end up caching nothing, and the problem has a narrower solution.

Rather than excluding every page that shows it, the count can be fetched separately after the page loads, so the page itself is cacheable and only the small piece is personal.

curl -s https://example.com/ | grep -oE 'cart-count[^<]*' | head
curl -s 'https://example.com/?wc-ajax=get_refreshed_fragments' | head -c 200

If that second address returns content, the mechanism is in use. If the count is rendered into the page instead, every page containing the header is personal and cannot be shared.

Getting this right is what allows a store to cache its catalogue at all, which is the difference between a shop that survives a busy day and one that does not.

Purge only what changed

Clearing everything after each product edit is common and it is why some stores are slow every afternoon.

A full clear means every page has to be rebuilt on its next request, all at once. On a large catalogue that is a substantial amount of work triggered by a one word change to a description.

wp cache flush
wp transient delete --expired

Most caching layers can clear a single address or a group. Use that for a product edit, and keep the full clear for changes that genuinely affect every page, such as a theme change.

Expired stored values are worth removing separately and regularly, since they accumulate in the database and are read on every request even after they have stopped being useful. Database growth covers what else builds up.