Key Takeaways
admin-ajax.phpis WooCommerce’s biggest hidden bottleneck — every AJAX call loads the entire WordPress stack, taking 300–800ms even for simple operations- Cart fragments (
wc-ajax=get_refreshed_fragments) fire on every page load and bypass page caching — disable them on non-shop pages to save 300–800ms site-wide - AJAX add-to-cart on archive pages avoids full page reloads but can block the main thread for 200–400ms — debounce rapid clicks and show instant visual feedback
- WooCommerce’s REST API (wp-json) is 2–3x faster than admin-ajax.php for custom AJAX operations because it skips loading the admin context
- AJAX-powered product filters are essential for large catalogues but must use lightweight endpoints — a filter that reloads the full page defeats the purpose
The admin-ajax.php bottleneck
Every AJAX request in a default WooCommerce store routes through admin-ajax.php. This file was designed for the WordPress admin dashboard, not for high-traffic frontend operations. When a customer adds a product to their cart, updates a quantity, or applies a coupon, the request hits admin-ajax.php, which loads the entire WordPress bootstrap: configuration, database connection, all active plugins, the theme, and every registered hook. This is part of the broader WooCommerce speed optimisation challenge — WordPress was built for content management, not real-time ecommerce interactions.
The result is that even a trivial AJAX operation — returning a cart item count — takes 300–800ms on typical shared hosting. On a busy store, dozens of concurrent AJAX requests compete for PHP workers, creating a queue that slows everything down. Understanding this bottleneck is fundamental to optimising WooCommerce performance.
WooCommerce partially addresses this with its own AJAX endpoint (?wc-ajax=) which skips some of the admin-side loading, but it still bootstraps the full WordPress and plugin stack. The performance difference between admin-ajax.php and ?wc-ajax= is typically only 50–100ms.
Cart fragments: the hidden performance killer
Cart fragments are WooCommerce’s mechanism for keeping the mini-cart widget synchronised across pages. On every page load — your homepage, blog posts, about page, literally every page — WooCommerce fires an AJAX request to ?wc-ajax=get_refreshed_fragments. This endpoint loads the full WordPress stack, rebuilds the cart HTML, and returns it as a JSON response.

The problem is twofold. First, this AJAX call bypasses your page cache entirely. Your homepage might be served from Varnish in 50ms, but the fragments call hits PHP and takes 400–800ms regardless. Second, it runs on every page, even pages where nobody interacts with their cart. Your blog post about product care instructions doesn’t need a live-updating cart widget.
The standard fix disables cart fragments on non-shop pages, as we covered in our checkout speed guide:
add_action('wp_enqueue_scripts', function() {
if (!is_woocommerce() && !is_cart() && !is_checkout()) {
wp_dequeue_script('wc-cart-fragments');
}
});
For a more sophisticated approach, replace the heavy fragments call with a lightweight cart count endpoint:
// Register a lightweight cart count endpoint
add_action('wp_ajax_vp_cart_count', 'vp_cart_count');
add_action('wp_ajax_nopriv_vp_cart_count', 'vp_cart_count');
function vp_cart_count() {
wp_send_json(['count' => WC()->cart->get_cart_contents_count()]);
}
This custom endpoint returns a single integer instead of the full rendered cart HTML — it’s 10–20x smaller than the fragments response and 2–3x faster to generate. Your JavaScript then updates just the cart count badge rather than replacing the entire mini-cart DOM.
The trade-off is that the mini-cart dropdown won’t show product thumbnails and item details without opening the cart page. For most stores, showing “Cart (3)” in the header and letting customers click through to the full cart page is perfectly acceptable and dramatically faster.
AJAX add-to-cart optimisation
WooCommerce’s AJAX add-to-cart on shop/archive pages avoids a full page reload when customers add products to their cart. Instead of navigating to the single product page, adding to cart, and being redirected back, the customer clicks “Add to Cart” and stays on the same page while an AJAX request processes the addition.
The default implementation works but has performance issues. The AJAX call takes 300–600ms to process (full WordPress bootstrap + cart recalculation), during which there’s no visual feedback. If a customer clicks “Add to Cart” twice because they think the first click didn’t work, two AJAX requests fire simultaneously, potentially adding the product twice.
Fix both issues with immediate visual feedback and request debouncing:
// Prevent double-click issues on add-to-cart buttons
jQuery(document).on('click', '.ajax_add_to_cart', function(e) {
var $btn = jQuery(this);
if ($btn.hasClass('loading')) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
});
For single product pages, AJAX add-to-cart isn’t enabled by default — clicking “Add to Cart” triggers a form submission and full page reload. Enabling AJAX on single product pages saves the reload but requires careful implementation. The customer needs clear feedback that the product was added, and the page must handle variations, quantities, and grouped products correctly.
The product page speed impact of AJAX add-to-cart depends on what happens after the product is added. If the response triggers a full cart fragments refresh (the default behaviour), you’ve saved a page reload but added a 400–800ms AJAX call. If you return only the updated cart count and show a toast notification, the perceived performance is much better.
AJAX product filter performance
Product filters (price range, colour, size, brand) are essential for stores with large catalogues. The performance of these filters determines whether customers can quickly find what they want or abandon your site in frustration.
The worst implementation is a filter that triggers a full page reload on every selection. The customer selects “Red”, waits 2–3 seconds for the page to reload, then selects “Large” and waits another 2–3 seconds. Six filter selections means 12–18 seconds of waiting.
AJAX-powered filters avoid page reloads but introduce their own performance challenges. Each filter selection triggers an AJAX request that must query the database, apply all selected filters, count results, and return the updated product grid HTML. On stores with 10,000+ products and complex filter combinations, these queries can take 500ms–2s.
Optimise filter queries by using product lookup tables instead of postmeta joins:
// Use WooCommerce's lookup table for faster filtering
add_filter('woocommerce_product_query_meta_query', function($meta_query) {
// WooCommerce 8.0+ uses wp_wc_product_meta_lookup
// Ensure your filters query this table, not wp_postmeta
return $meta_query;
});
Cache filter counts. When displaying “Red (24)” next to the colour filter, the count requires a database query. These counts change infrequently — cache them for 1 hour and invalidate when products are updated.
Return minimal HTML in filter responses. Instead of returning the full page HTML including header, footer, and sidebar, return only the product grid and pagination. Use JavaScript to replace just the product grid container. This reduces the AJAX response size by 60–80% and the server-side rendering time by 40–50%.
AJAX search optimisation
Live product search (showing results as the customer types) is one of the most performance-sensitive AJAX operations in WooCommerce. Each keystroke can trigger a database query searching product titles, descriptions, SKUs, and attributes.
Without debouncing, typing “blue dress” fires 10 separate AJAX requests — one for “b”, one for “bl”, one for “blu”, and so on. Each request loads the WordPress stack and runs database queries. On busy stores, this creates a cascade of PHP worker consumption.
Essential search AJAX optimisations:
// Client-side: debounce search input (vanilla JS)
let searchTimeout;
document.querySelector('.search-field').addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function() {
// Only fire AJAX after 300ms of no typing
performSearch(e.target.value);
}, 300);
});
On the server side, limit the search scope. Searching product titles and SKUs is fast. Searching full product descriptions and short descriptions adds significant query time. For live search, query only titles and SKUs — the customer can use the full search results page for deeper searches.
Cache search results for common queries. If “dress” gets searched 50 times per hour, there’s no reason to run the same database query each time. A 15-minute transient cache for the top 100 search terms eliminates most redundant queries. This aligns with the plugin performance principle of reducing unnecessary database operations.
Migrating from admin-ajax to the REST API
WordPress’s REST API (/wp-json/) offers significant performance advantages over admin-ajax.php for custom AJAX operations. The REST API skips loading the admin context, supports proper HTTP caching headers, and provides built-in request validation and sanitisation.
For custom WooCommerce AJAX operations (wishlists, product comparisons, store locators), building REST API endpoints instead of admin-ajax handlers typically saves 100–200ms per request:
// Register a REST API endpoint instead of using admin-ajax
add_action('rest_api_init', function() {
register_rest_route('vp/v1', '/wishlist', array(
'methods' => 'POST',
'callback' => 'vp_handle_wishlist',
'permission_callback' => function() {
return is_user_logged_in();
},
));
});
function vp_handle_wishlist($request) {
$product_id = absint($request->get_param('product_id'));
// Handle wishlist logic...
return new WP_REST_Response(['success' => true], 200);
}
The REST API also supports conditional requests with ETags and Last-Modified headers. For read-heavy endpoints (product data, store locations, FAQ content), this means the browser can cache responses and skip the server entirely on repeat requests.
WooCommerce’s own REST API endpoints (products, orders, customers) are designed for admin/integration use and aren’t optimised for frontend performance. Don’t use the WooCommerce REST API for customer-facing AJAX — build lightweight custom endpoints that return only the data your frontend needs.
Mini-cart and side-cart performance
Side carts (slide-out cart drawers) have become the standard UX pattern for WooCommerce stores. They let customers review their cart without leaving the current page. But side carts introduce significant performance overhead if implemented poorly.
The worst implementation pre-renders the full side cart HTML on every page load, hidden with CSS. This adds 5–15KB of DOM elements to every page, plus the JavaScript to manage interactions. Combined with cart fragments (which update this hidden cart on every page load), you’ve got two performance penalties running simultaneously.
The optimised approach renders the side cart only when triggered. When the customer clicks the cart icon, JavaScript fetches the current cart contents via a lightweight AJAX call and renders the side cart UI client-side. This means zero performance cost on page load — the overhead only occurs when the customer actually wants to see their cart.
// Only fetch cart data when side cart is opened
document.querySelector('.cart-icon').addEventListener('click', function() {
fetch('/?wc-ajax=get_refreshed_fragments')
.then(r => r.json())
.then(data => {
document.querySelector('.side-cart').innerHTML =
data.fragments['.widget_shopping_cart_content'];
document.querySelector('.side-cart').classList.add('open');
});
});
This pattern eliminates the need for cart fragments on page load entirely. The trade-off is a 300–500ms delay when opening the side cart for the first time, but subsequent opens can use cached data until the cart changes.
Evaluate your side cart plugin’s impact using the techniques in our INP optimisation guide. Many popular side cart plugins (WooCommerce Side Cart, CartFlows) load 50–100KB of JavaScript on every page regardless of whether the side cart is ever opened.
What is admin-ajax.php in WooCommerce?
admin-ajax.php is WordPress’s built-in AJAX handler that processes all asynchronous requests. WooCommerce uses it for cart updates, coupon applications, checkout processing, and other dynamic operations. The performance problem is that every request to admin-ajax.php loads the entire WordPress stack — all plugins, the theme, and database connections — making even simple operations take 300–800ms.
How do I fix slow WooCommerce AJAX requests?
Three approaches: First, disable cart fragments on non-shop pages — this eliminates the biggest unnecessary AJAX call. Second, replace admin-ajax.php handlers with REST API endpoints for custom functionality, saving 100–200ms per request. Third, implement client-side debouncing for search and filter AJAX calls to prevent cascading requests. For cart operations that must use WooCommerce’s AJAX, ensure your server has enough PHP workers and Redis object caching to handle the load.
Should I disable cart fragments in WooCommerce?
You should disable cart fragments on non-shop pages. There’s no reason for your blog posts, about page, or contact page to make a 400–800ms AJAX call to update a mini-cart that nobody interacts with on those pages. Keep cart fragments enabled on WooCommerce pages (shop, product, cart, checkout) where the mini-cart widget is actually useful. This single change typically saves 300–800ms on every non-shop page load across your entire site.
Is the WordPress REST API faster than admin-ajax?
Yes, the REST API is typically 100–200ms faster per request because it skips loading the admin context. It also supports HTTP caching headers (ETags, Last-Modified), enabling browser-side caching that admin-ajax.php doesn’t support. For new custom AJAX functionality, always use the REST API. However, WooCommerce’s core operations (add to cart, update cart, checkout) still use the WooCommerce AJAX endpoint and can’t easily be moved to the REST API without significant custom development.
How do I optimise WooCommerce product search speed?
Implement three optimisations: debounce search input with a 300ms delay so you don’t fire AJAX on every keystroke, limit the search scope to product titles and SKUs (skip searching full descriptions for live results), and cache popular search queries with 15-minute transients. For stores with 5,000+ products, consider a dedicated search service like Algolia or Elasticsearch that handles search outside of WordPress entirely, reducing response times from 300–500ms to 20–50ms.
AJAX slowing down your store?
Get your free WooCommerce speed audit
We’ll identify every AJAX bottleneck in your store, from cart fragments to search queries, and show you exactly what to fix — with guaranteed performance improvements.