The single biggest cause of slow WooCommerce sites is a set of cart cookies almost nobody talks about.
I’ve audited and rebuilt a lot of WooCommerce sites, and the same pattern shows up on nearly all of them: the site benchmarks fast, real shoppers experience it slow, and the performance audit the owner paid for never mentions why. The culprit is WooCommerce’s cart session handling, which quietly disables page caching for exactly the visitors who matter most, the ones with something in their cart.
Based on the feedback I’ve gotten over the years, this rarely comes up in WooCommerce performance audits. It should be the first thing checked.
What WooCommerce Is Actually Doing
The moment a visitor adds an item to their cart, WooCommerce sets cookies in their browser:
woocommerce_items_in_cart: the number of items in the cartwoocommerce_cart_hash: a hash of the cart contents, used to detect changeswp_woocommerce_session_*: the session cookie that ties the browser to cart data stored in the database
The reasoning is completely sensible. Your theme almost certainly has a cart icon in the header, top right, like most WooCommerce themes do. To show that cart count on any page of the site, WooCommerce needs to know who this visitor is and what’s in their cart on every single request. The cookies are how it does that.
The problem is what those cookies do to caching.
Why This Bypasses Your Page Cache
Full page caching works by serving the same saved copy of a page to everyone. That breaks the moment pages contain per-visitor content, because you can’t serve one shopper a page showing someone else’s cart.
Every serious caching layer knows this, so they all do the same thing: when they see WooCommerce cart or session cookies on a request, they skip the cache entirely.
- WP Engine, Kinsta and most managed WordPress hosts exclude requests carrying
woocommerce_items_in_cartor a session cookie from their server-side cache - Varnish configurations ship with the same exclusion rules
- Cloudflare page rules and APO bypass on these cookies as well
Think through what that means in practice:
- A visitor lands on your homepage. Cached, fast, great.
- They add one item to their cart. The cookies get set.
- Every page they view from that point on is uncached. Product listings, the blog, the about page, checkout, all of it now boots full WordPress, loads every plugin, and runs hundreds of database queries per page view.
And because that cart icon lives in the header, this isn’t limited to shop pages. The cookie-based bypass applies to effectively every page on your site. Your fastest visitors are the ones who never shop. The people actively trying to give you money get the slowest version of your site, at exactly the moment conversion matters.
Cart Fragments Make It Worse
On top of the cookie bypass, WooCommerce ships a feature called cart fragments: an AJAX request (wc-ajax=get_refreshed_fragments) that fires on page load to refresh the cart count in the header. That request can never be cached, boots WordPress every time it runs, and fires for visitors with empty carts too. On busy sites it’s a steady stream of expensive requests doing nothing but asking “is the cart still empty?”
Why Performance Audits Miss This
Nearly every performance test is run as an anonymous visitor with an empty cart. Lighthouse, PageSpeed Insights, GTmetrix, the audit spreadsheet a consultant fills in: all of them hit your cached pages and report healthy numbers.
The slow experience only exists for visitors with cart cookies, and no standard tool simulates that. Add a product to your cart, then browse your own site with the Network panel open and watch the TTFB on every page jump from tens of milliseconds to multiple seconds. That’s the site your actual customers are using.
The Fix: Cacheable Pages + a Lightweight API
The approach I use flips the architecture: every page is fully cacheable for everyone, and the dynamic pieces are fetched separately with small AJAX calls.
The page itself never contains per-visitor content. The cart count in the header starts empty and is filled in by JavaScript after the page loads:
// After page load, fetch the visitor's cart state
fetch('/api/cart/', { credentials: 'same-origin' })
.then((r) => r.json())
.then(({ count }) => {
document.querySelector('.header-cart-count').textContent = count > 0 ? count : '';
});
The response is a tiny JSON payload:
{ "count": 3, "total": "84.00" }
That’s the whole trick, but the details are where the performance comes from.
Keep the API Outside WordPress
If the cart-count endpoint boots full WordPress, you’ve just moved the problem. The real win is handling it completely separately from WordPress: a small standalone PHP endpoint that reads the session cookie and looks up the cart directly, without loading the theme, the plugin stack, or running the hundreds of queries a WordPress bootstrap triggers.
WooCommerce stores session data in a single database table, wp_woocommerce_sessions, keyed by the customer ID in the session cookie. A standalone endpoint needs one database connection and one indexed query to answer “how many items does this visitor have?” That’s a response in a few milliseconds, versus a full WordPress load measured in hundreds of milliseconds to seconds.
Tell Your Cache to Ignore the Cookies
With the dynamic parts moved into the API call, the cart cookies no longer need to bypass anything. Configure your caching layer to ignore the WooCommerce cookies on regular pages so shoppers get the same cached pages as everyone else. Only the genuinely dynamic endpoints, the cart page, checkout and the API itself, stay uncached.
Disable Cart Fragments
With your own endpoint in place, the built-in fragments request is pure overhead:
// Remove WooCommerce cart fragments entirely
add_action('wp_enqueue_scripts', function () {
wp_dequeue_script('wc-cart-fragments');
}, 11);
Cache the Count in the Browser Too
One more refinement: store the count in sessionStorage after each fetch and render it instantly on the next page view, revalidating in the background. The header never flickers, and visitors who never touch the cart never trigger a request at all.
What This Changes in Practice
- Shoppers get cached pages. The visitors with items in their cart, your highest-intent traffic, go from full WordPress bootstraps on every page to the same fast cached responses as everyone else.
- Database load collapses. Pages that made hundreds of queries per view for every shopper now make zero, and the API replacing them makes one.
- The site survives traffic spikes. Cached pages plus a millisecond API endpoint scale in a way uncached WordPress never will, which matters most exactly when a sale or campaign is driving buyers.
This is a rarely performed but extremely beneficial way to build a WooCommerce site, and it pairs naturally with the minimal custom theme approach I’ve written about in how I build custom WordPress themes. If your store feels slow and no audit has explained why, add something to your cart and look at your TTFB before you spend another dollar on optimization plugins. It’s also exactly the kind of architecture work I take on in custom WordPress eCommerce engagements, because the difference for a store that depends on conversion is not subtle.