This guide covers exclusively the WooCommerce speed optimization techniques that don't apply to a regular WordPress site - problems specific to online stores: AJAX cart fragments, sessions bloating the database, Action Scheduler, cache exclusions on checkout, and HPOS. For general WordPress optimization fundamentals (hosting, Redis, CDN, WebP, Core Web Vitals, compression), see the complete WordPress site optimization guide - those techniques are prerequisites, we don't duplicate them here.
A standard WooCommerce store, without optimization, loads in 4-7 seconds on mobile - enough to lose 53% of visitors (Google data, 2023). WooCommerce adds significant complexity compared to regular WordPress: AJAX cart fragments on every page, thousands of specific transients in the database, heavy queries for variable products, and sessions per visitor. All techniques are tested on WooCommerce 9.x with active HPOS, PHP 8.2+, on projects delivered by Creative Side.
General Techniques Applied to WooCommerce
Before the specific techniques below, make sure you have the fundamentals in place:
With the fundamentals in place, here are the 8 techniques that matter only on WooCommerce.
1. Disable Cart Fragments AJAX on Non-Shop Pages
Cart fragments is the single biggest WooCommerce performance killer. The wc-cart-fragments script makes an AJAX request on every page load - including on blog, homepage, and presentation pages - to update the cart icon in the header. On a site without caching, this request adds 0.5-1.5 seconds to every page.
Why It's WooCommerce-Specific
This script doesn't exist on WordPress without WooCommerce. It's automatically loaded by WooCommerce on all frontend pages, regardless of whether the page has anything to do with the shop. On stores with 500+ simultaneous visitors, cart fragments generate 500-1,500 unnecessary AJAX requests per minute.
Fix
add_action('wp_enqueue_scripts', function () {
if (!is_cart() && !is_checkout() && !is_product() && !is_shop() && !is_product_category()) {
wp_dequeue_script('wc-cart-fragments');
}
}, 100);
Alternative for Stores That Need a Global Cart Counter
If you need a cart counter on all pages (a badge with the number of products in the cart), replace cart fragments with a server-side generated static mini-cart:
// Replace cart fragments with a lightweight REST endpoint
add_action('wp_enqueue_scripts', function () {
if (!is_cart() && !is_checkout()) {
wp_dequeue_script('wc-cart-fragments');
}
}, 100);
// Mini-cart count via REST API (called on 'added_to_cart' JS event)
add_action('rest_api_init', function () {
register_rest_route('cs/v1', '/cart-count', [
'methods' => 'GET',
'callback' => function () {
return ['count' => WC()->cart ? WC()->cart->get_cart_contents_count() : 0];
},
'permission_callback' => '__return_true',
]);
});
This approach eliminates the AJAX request at page load and only makes a request when the user adds a product - reduction from 100% of pageviews to ~2-5%.
Benchmark: On a store with 8,000 visits/day, disabling cart fragments reduced AJAX requests by 7,200/day and decreased TTFB on non-shop pages from 1.8s to 0.6s.
2. Clean Up Expired WooCommerce Sessions
WooCommerce creates a database session for every visitor - including bots, crawlers, and security scanners. The wp_woocommerce_sessions table grows unlimited if not cleaned up. We've seen stores with 200,000+ expired sessions that added 500ms to every query.
Why They Accumulate
WooCommerce has an internal cleanup cron job (woocommerce_cleanup_sessions), but on hosting without real cron (wp-cron based on traffic), it doesn't run consistently. On stores with intermittent traffic (heavy during the day, zero at night), sessions accumulate faster than they're cleaned.
Diagnostic
-- Count expired sessions
SELECT COUNT() FROM wp_woocommerce_sessions
WHERE session_expiry < UNIX_TIMESTAMP();
-- Total table size
SELECT
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
table_rows
FROM information_schema.tables
WHERE table_name = 'wp_woocommerce_sessions';
If the table exceeds 50MB or has over 50,000 expired rows, cleanup has a visible impact.
Fix: Forced Scheduled Cleanup
add_action('init', function () {
if (!wp_next_scheduled('cs_cleanup_wc_sessions')) {
wp_schedule_event(time(), 'twicedaily', 'cs_cleanup_wc_sessions');
}
});
add_action('cs_cleanup_wc_sessions', function () {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}woocommerce_sessions WHERE session_expiry < %d",
time()
)
);
});
Edge Case: Stores with 10,000+ Daily Visitors
On high-traffic stores, twicedaily cleanup may not be enough. Add a system-level cron (not wp-cron) that runs every hour:
# crontab -e
0 cd /var/www/html && wp wc session cleanup --allow-root > /dev/null 2>&1
Benchmark: On a store with 12,000 visits/day, the sessions table had grown to 340MB with 890,000 rows. After cleanup, the table dropped to 8MB. TTFB on product pages decreased from 1.4s to 0.7s (before other optimizations).
3. Action Scheduler Cleanup (WooCommerce Logs)
WooCommerce uses Action Scheduler as its internal cron system - it manages emails, stock synchronization, order processing, webhooks, and analytics. The wp_actionscheduler_actions table grows by 10,000-50,000 rows per month on active stores. On a store with 2+ years of operation, this table can contain 500,000+ rows.
Diagnostic
-- Count actions per status
SELECT status, COUNT() as total
FROM wp_actionscheduler_actions
GROUP BY status;
-- Table size
SELECT
ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb,
table_rows
FROM information_schema.tables
WHERE table_name = 'wp_actionscheduler_actions';
Actions with complete and failed status can be safely deleted. Those with pending status are scheduled and should not be touched.
Fix: Reduce Retention
// Change retention from 30 days (default) to 7 days
add_filter('woocommerce_delete_completed_actions_after', function () {
return DAY_IN_SECONDS 7;
});
Aggressive Fix for Stores with Bloated Tables (500,000+ Rows)
Clean up manually from WP-CLI or directly via SQL:
# From WP-CLI - delete completed actions older than 7 days
wp action-scheduler clean --batch-size=1000 --status=complete
-- Direct SQL cleanup (mandatory backup before)
DELETE FROM wp_actionscheduler_actions
WHERE status IN ('complete', 'failed')
AND scheduled_date_gmt < DATE_SUB(NOW(), INTERVAL 7 DAY)
LIMIT 50000;
-- Run multiple times if you have hundreds of thousands of rows (in 50k batches)
Benchmark: On a store with 680,000 completed actions (420MB table), cleanup reduced the table to 12MB. WooCommerce Analytics queries reading from wp_actionscheduler_actions dropped from 3.2s to 0.1s.
4. WooCommerce-Specific Cache Exclusions
Cart, checkout, and my-account pages contain user-specific data (cart contents, payment session, order history) and cannot be cached. If you cache them, visitor A sees visitor B's cart. But product, category, and shop pages can and should be cached aggressively.
Exclusion Rules for WP Rocket
// Settings → WP Rocket → Advanced Rules → Never Cache URLs
/cart/(.)
/checkout/(.)
/my-account/(.)
/wishlist/(.)
// Never Cache Cookies
woocommerce_cart_hash
woocommerce_items_in_cart
wp_woocommerce_session_(.)
Exclusion Rules for LiteSpeed Cache
// LiteSpeed Cache → Cache → Excludes → Do Not Cache URIs
/cart/
/checkout/
/my-account/
/wishlist/
// LiteSpeed Cache → Cache → Excludes → Do Not Cache Cookies
woocommerce_cart_hash
woocommerce_items_in_cart
ESI (Edge Side Includes) on Checkout
The full checkout cannot be cached, but static sections (header, footer, terms & conditions) can be served from cache. LiteSpeed Cache supports ESI natively:
// Enable ESI in LiteSpeed Cache → General → ESI
// Header and footer are served from ESI cache
// Checkout body is generated dynamically per user
do_action('litespeed_control_set_nocache');
Edge Case: Stores with Geolocation-Based Pricing
If you use location-based pricing (WooCommerce Multilingual or a geolocation plugin), you need to vary the cache by country. In LiteSpeed Cache, add the geolocation cookie to "Vary Cookie":
wc_geo_country
Benchmark: Correctly configuring cache exclusions on a store with LiteSpeed increased the cache hit rate from 45% to 87%. Product pages are served from cache in 80ms instead of 1.2s.
5. Asynchronous Variation Loading (AJAX Variation Loading)
Variable products (size, color, material) generate a JSON with all possible variation combinations at page load. A product with 3 attributes x 10 values each = 1,000 combinations = 200-500KB JSON. On stores with textile products (4-5 attributes per product), this JSON can reach 1MB per product page.
Fix: AJAX Variation Loading Threshold
// Over 10 variations, load via AJAX on selection (WooCommerce default: 30)
add_filter('woocommerce_ajax_variation_threshold', function () {
return 10;
});
Default value: 30. For complex products with 50+ variations, reduce to 10 or even 5. The user selects the first attribute, and only relevant variations are loaded via AJAX.
Edge Case: Stores with 10,000+ Variable Products
On fashion stores with 5,000+ variable products, the cumulative impact is enormous. A product with 80 variations sends ~400KB JSON. On a category page with 24 variable products (even if you don't display the variations), WooCommerce pre-processes the data. The additional solution:
// Disable variation preload on category/shop pages
add_filter('woocommerce_product_get_default_attributes', function ($defaults, $product) {
if (is_shop() || is_product_category() || is_product_tag()) {
return [];
}
return $defaults;
}, 10, 2);
Benchmark: On a fashion store with 3,200 variable products (average 45 variations/product), reducing the threshold to 10 decreased product page size from 1.8MB to 320KB. Time to Interactive dropped from 6.2s to 2.8s on mobile.
6. Disable Heartbeat API on Frontend
WordPress Heartbeat API makes AJAX requests every 15-60 seconds. On the frontend, it's completely useless for an online store - it consumes server resources with zero benefit.
Fix
add_action('init', function () {
// Completely disable on frontend
if (!is_admin()) {
wp_deregister_script('heartbeat');
}
});
// Reduce frequency in admin (from 15s to 60s) to not affect autosave
add_filter('heartbeat_settings', function ($settings) {
$settings['interval'] = 60;
return $settings;
});
Why It Matters More on WooCommerce Than on Simple WordPress
A presentation site has short visits (2-3 minutes). An online store has long visits - the user browses for 10-20 minutes, compares products, reads reviews. During those 20 minutes, Heartbeat makes 20-80 AJAX requests per open tab. On a store with 500 simultaneous visitors, that means 10,000-40,000 unnecessary requests per minute.
Benchmark: On a store with 800 simultaneous visitors at peak hour, disabling Heartbeat on the frontend reduced AJAX requests by ~65% and freed up 2 PHP workers that were constantly occupied with Heartbeat.
7. Disable WooCommerce Scripts/Styles on Non-Shop Pages
WooCommerce loads CSS and JS on every page of the site - blog, About Us, Contact, portfolio. On a site with 50% non-shop content, that means unnecessary CSS/JS on half of all pages.
Fix: Selective Dequeue
add_action('wp_enqueue_scripts', function () {
if (!is_woocommerce() && !is_cart() && !is_checkout() && !is_account_page()) {
// Disable WooCommerce CSS
wp_dequeue_style('woocommerce-general');
wp_dequeue_style('woocommerce-layout');
wp_dequeue_style('woocommerce-smallscreen');
wp_dequeue_style('wc-blocks-style');
wp_dequeue_style('wc-blocks-vendors-style');
// Disable WooCommerce JS
wp_dequeue_script('wc-add-to-cart');
wp_dequeue_script('wc-cart-fragments');
wp_dequeue_script('woocommerce');
wp_dequeue_script('jquery-blockui');
wp_dequeue_script('jquery-cookie');
wp_dequeue_script('wc-add-to-cart-variation');
wp_dequeue_script('wc-single-product');
}
}, 99);
Edge Case: Themes That Use WooCommerce CSS on Non-Shop Pages
Some themes (Astra, OceanWP) reuse WooCommerce CSS classes on non-shop pages (e.g., .woocommerce-message for notifications). If the dequeue breaks layout on other pages, add exceptions:
// Exception for pages using WooCommerce shortcodes
if (!is_woocommerce() && !is_cart() && !is_checkout() && !is_account_page()
&& !has_shortcode(get_post()->post_content ?? '', 'products')
&& !has_shortcode(get_post()->post_content ?? '', 'recent_products')) {
// dequeue here
}
Benchmark: On a store with 120 pages (40 shop pages + 80 content pages), dequeuing WooCommerce assets eliminated 6 CSS files and 5 JS files from non-shop pages. Reduction: 180KB less to download, 4 fewer HTTP requests. PageSpeed mobile on the Blog page increased from 72 to 91.
8. HPOS (High-Performance Order Storage)
HPOS is the new order storage system in WooCommerce 9.x - it moves orders from the wp_posts table (shared with articles, pages, products, attachments) into a dedicated wp_wc_orders table. The result: order queries isolated from the rest of the content, with optimized indexing.
Why It Matters
The wp_posts table on a mature store contains: 5,000 products + 50,000 orders + 200,000 attachments + 10,000 articles/pages = 265,000 rows. Order queries have to filter through all these rows. With HPOS, orders have their own table with dedicated indexing.
Activation
WooCommerce → Settings → Advanced → Features → "Order data storage" = "High-Performance Order Storage".
What to Check Before Activation
wp_posts and wp_wc_orders. Disable after you've confirmed everything works (compatibility mode doubles writes).HPOS-Compatible Code for Custom Plugins
If you have custom plugins that access orders, they need to be migrated from get_post_meta() to the HPOS API:
// Old (pre-HPOS) - NO LONGER works with active HPOS
$order_total = get_post_meta($order_id, '_order_total', true);
// New (HPOS-compatible)
$order = wc_get_order($order_id);
$order_total = $order->get_total();
// Query orders - old
$orders = get_posts([
'post_type' => 'shop_order',
'post_status' => 'wc-completed',
'meta_key' => '_billing_email',
'meta_value' => 'client@email.com',
]);
// Query orders - HPOS-compatible
$orders = wc_get_orders([
'status' => 'completed',
'billing_email' => 'client@email.com',
'limit' => -1,
]);
Benchmark: On a store with 80,000 historical orders and 5,000 products:
wc_get_orders() on 1,000 orders: from 1.8s to 0.15sIs your WooCommerce store loading in over 3 seconds? Creative Side audits and optimizes WooCommerce stores - measurable results on every metric.
Aggregate Results: Before and After
Results from a real WooCommerce store with 3,200 variable products, 80,000 orders, on a VPS with LiteSpeed + Redis, after applying all 8 techniques:
| Metric | Before | After | Main Techniques |
|---|---|---|---|
| Product page TTFB | 2.3s | 0.4s | Redis + HPOS + session cleanup |
| Blog page TTFB (non-shop) | 1.8s | 0.3s | Cart fragments off + dequeue assets |
| SQL queries / product page | 340 | 18 | Redis + HPOS + variation threshold |
| AJAX requests / minute (500 visitors) | 2,800 | 180 | Cart fragments off + Heartbeat off |
| Total page size (product) | 1.8MB | 320KB | Dequeue assets + variation AJAX |
| PageSpeed Mobile (homepage) | 28 | 87 | All of the above |
Frequently Asked Questions
Is WooCommerce Too Slow for Large Stores?
No. WooCommerce handles stores with 100,000+ products and millions of orders - but not on shared hosting with zero optimization. With active HPOS, Redis, CDN, and correctly configured caching, WooCommerce is competitive with any SaaS platform. The difference is that it needs to be configured manually, it doesn't come optimized out of the box.
How Much Does WooCommerce Store Optimization Cost?
A complete performance audit with implementation (caching, Redis, CDN, database cleanup, dequeue assets, HPOS) costs between 3,000 and 8,000 lei depending on complexity. The investment pays for itself through increased conversion rates - a store that loads in 2s instead of 5s converts 20-30% more (pricing details).
WP Rocket or LiteSpeed Cache for WooCommerce?
If the server runs LiteSpeed or OpenLiteSpeed, use LiteSpeed Cache - it's free and integrates natively with the server, including ESI on checkout. If the server is Apache or Nginx, WP Rocket is the easiest to configure for WooCommerce, with pre-configured exclusion rules for cart/checkout.
Do I Need to Apply All 8 Techniques?
Not necessarily. Prioritize by impact: (1) Cart Fragments, (2) HPOS, (3) cache exclusions, (4) session cleanup, (5) Action Scheduler, (6) dequeue assets, (7) variation threshold, (8) Heartbeat. On a small store (under 500 products), techniques 5 and 7 have minimal impact. On a large store (5,000+ products, 100,000+ orders), all 8 are necessary.
Next Step
A fast WooCommerce store sells more - every 100ms gained in loading time increases conversions by 0.7% (Amazon data). Optimization is not a cost, it's an investment with direct return.
If you have a WooCommerce store that loads in over 3 seconds, the Creative Side team can bring it under 2 seconds with a complete optimization package - the 8 techniques above plus the fundamentals from the general guide.
Request WooCommerce optimization - audit + implementation with guaranteed results