Problem
Shipped a WooCommerce 9.6 store for a client on Thursday. Block theme, Cart and Checkout blocks, custom product grid on the homepage. Customer clicks "Add to Cart" on a product card, the button spins, the success message appears, but the mini cart in the header still reads "0". Only a full page refresh updates the counter. On mobile the off-canvas drawer opens empty.
I've seen four threads about this on r/Wordpress in the last week and another on the Woo GitHub issue tracker. If your counter is frozen after AJAX add-to-cart, this is almost always the same root cause.
Why It Happens
WooCommerce updates the mini cart through a legacy feature called cart fragments. When the woocommerce-add-to-cart event fires, the wc-cart-fragments.js script posts to admin-ajax.php?wc-ajax=get_refreshed_fragments, gets back a JSON payload of HTML snippets keyed by CSS selectors, and swaps each fragment into the DOM.
// Response looks like this:
{
"fragments": {
"div.widget_shopping_cart_content": "<div class='widget_shopping_cart_content'>...</div>",
"a.cart-contents": "<a class='cart-contents'>2 items</a>"
},
"cart_hash": "b2f1..."
}
Three things break this in a block theme setup:
- The selector in the fragment payload does not exist in your markup. Classic themes used
a.cart-contentsanddiv.widget_shopping_cart_content. Block themes render the mini cart with the Cart block or a custom pattern that uses entirely different classes —wc-block-mini-cart__button,wc-block-mini-cart__badge. The fragment response has no key matching those, so jQuery finds nothing to replace. - jQuery is not loaded.
wc-cart-fragments.jsstill depends on jQuery. If your theme dequeued jQuery to pass Lighthouse, the fragment script fails silently and every AJAX response is thrown away. - The cart cookie is being cached. Edge caches (Cloudflare APO, LiteSpeed, WP Rocket page cache) cache the
woocommerce_cart_hashcookie response, so every visitor sees the same stale count.
Quickest way to confirm: open devtools, add something to cart, find the get_refreshed_fragments request. If the 200 response has the fragments object but your cart counter never moves, it's a selector mismatch. If the request never fires, jQuery is the issue. If the response returns the wrong cart count, it's cache.
The Fix
Three fixes depending on which cause you hit. I ran into all three on the same project last week.
1. Register your mini cart selectors as fragments.
Tell WooCommerce which block-theme selectors to refresh. Drop this in your child theme's functions.php or a small mu-plugin:
// wp-content/mu-plugins/mini-cart-fragments.php
add_filter('woocommerce_add_to_cart_fragments', function ($fragments) {
ob_start();
$count = WC()->cart->get_cart_contents_count();
$total = WC()->cart->get_cart_subtotal();
?>
<span class="wc-block-mini-cart__badge"><?php echo esc_html($count); ?></span>
<?php
$fragments['span.wc-block-mini-cart__badge'] = ob_get_clean();
ob_start();
?>
<span class="mini-cart-total"><?php echo wp_kses_post($total); ?></span>
<?php
$fragments['span.mini-cart-total'] = ob_get_clean();
return $fragments;
});
The selector key on the left has to match the element that already exists in your rendered HTML. Inspect the cart button, copy the real class, use that as the array key. Anything inside the buffered markup becomes the replacement.
2. Make sure jQuery and cart-fragments are enqueued.
If your theme is aggressive about removing jQuery, re-enqueue it specifically for pages where WooCommerce is active:
add_action('wp_enqueue_scripts', function () {
if (function_exists('is_woocommerce') && (is_cart() || is_shop() || is_product() || is_front_page())) {
wp_enqueue_script('jquery');
wp_enqueue_script('wc-cart-fragments');
}
}, 20);
I gate it on page type so the rest of the site stays jQuery-free. The header mini cart lives on the front page too, so include is_front_page() if that's where yours is.
3. Bust the cart cookie at the edge.
If you run Cloudflare APO or any full-page cache, add a bypass rule for any request carrying a WooCommerce cart cookie. In wp-config.php for WP Rocket:
// Force WP Rocket to skip cache when the cart has items
define('DONOTCACHEPAGE', true);
add_filter('rocket_cache_reject_cookies', function ($cookies) {
$cookies[] = 'woocommerce_items_in_cart';
$cookies[] = 'woocommerce_cart_hash';
$cookies[] = 'wp_woocommerce_session_';
return $cookies;
});
For Cloudflare, add a Cache Rule: "Bypass cache if cookie contains woocommerce_cart_hash". This is covered in the official Woo caching docs but most hosts do not ship it pre-configured.
After all three are in place, add something to cart, open the network panel, and verify you see the get_refreshed_fragments response, the selector match, and the updated number without a page reload.
Gotchas
Optimizer plugins defer-load wc-cart-fragments.js. Async loading this script breaks the add-to-cart event listener. Exclude it in your asset optimizer's ignore list.
The checkout block fires its own events. If you are on the Cart or Checkout block and the mini cart needs to stay in sync, listen for the wc-blocks_added_to_cart DOM event instead of relying only on jQuery(body).trigger('added_to_cart'). For Checkout Block specifics, see my WooCommerce Checkout Block payment methods fix.
Cart hash mismatch after login. If the count jumps to zero right after a customer logs in, the session cookie is rotating and the fragment cache still references the guest hash. Clear wp_woocommerce_session_ cookies on login via wp_login action.
Need a WooCommerce Dev To Dig Into Your Cart?
I fix AJAX cart bugs, block theme integrations, and cache conflicts on live stores without taking checkout offline. If your mini cart is stuck and sales are leaking, send me the store URL on my services page and I'll audit the fragment flow.