How to Speed Up WooCommerce
A store has two halves: catalogue pages that can be cached like any site, and cart, checkout and account pages that never can. Speed each half with different tools.
Some links on this page are affiliate links: if you buy through one, we may earn a commission at no extra cost to you. It does not change what we recommend; see the editorial policy.
A store is two sites with different speed problems: catalogue pages can be cached like any blog, while cart, checkout and account pages are rebuilt by PHP for every request. Fix the first half with a page cache and the second with the server, the database and fewer background jobs.
Most "WooCommerce is slow" advice treats the store as one thing and ends with "install a cache plugin". That helps the half that was already the easiest to make fast.
Split the store before you optimise
| Page | Cacheable for anonymous visitors? | What decides its speed |
|---|---|---|
| Home, category, tag, product | Yes | Page cache, images, scripts |
| Search and filtered listings | Partly — query strings often bypass the cache | Product queries, indexes, filter plugin |
| Cart, checkout, my-account | Never | PHP version, object cache, database, gateways |
| Any page for a logged-in customer | No | Same as above |
wp-admin, order screens |
No | Database, HPOS, plugins on admin hooks |
Measure one page from each row, logged out and in a private window, before changing anything. A product page at 300 ms and a checkout at 3 seconds are different jobs.
The cacheable half: catalogue and product pages
Treat these like any WordPress site, following how to speed up a WordPress site:
- Page cache. Use the host's server-level cache if it has one. On hosting without server-level caching, a plugin does the job — WP Rocket detects WooCommerce and excludes the cart, checkout and account pages automatically, which is the step people most often get wrong by hand. The general comparison is in choosing a cache plugin.
- Exclusions. Whatever caches the store must skip the cart, checkout and my-account URLs, and must bypass the cache when the
woocommerce_items_in_cart,woocommerce_cart_hashorwp_woocommerce_session_cookies are present. Without the URL exclusions, one customer can see another's cart; without the cookie bypass, cached pages show the wrong cart state. - Product images. Galleries with five full-size photos per product are the usual page-weight problem — see optimising images.
- Variations. A variable product with hundreds of variations loads them through AJAX above a threshold (30 by default). Raising the threshold (
woocommerce_ajax_variation_threshold) to avoid the request puts every variation into that product page's HTML instead.
Cart fragments
wc-cart-fragments keeps the header cart count current by requesting ?wc-ajax=get_refreshed_fragments whenever the browser has no stored copy or the cart changes. The request does not block rendering, but on a fully cached page it is often the only one that boots WordPress. On slow hosting with many new visitors, that is real server load.
Since WooCommerce 7.8, it only loads where the classic Cart widget is used, but older themes and plugins still enqueue it everywhere. If your header has no live cart count, or you accept the count updating on the next page load, dequeue it outside the cart and checkout:
<?php
// wp_footer, not wp_enqueue_scripts: the Cart widget enqueues the script
// while it renders, after wp_enqueue_scripts has already run.
add_action( 'wp_footer', function () {
if ( function_exists( 'is_woocommerce' ) && ! is_cart() && ! is_checkout() ) {
wp_dequeue_script( 'wc-cart-fragments' );
}
}, 1 );
Check the Network tab for a ?wc-ajax=get_refreshed_fragments request before and after.
The uncacheable half: cart, checkout and accounts
Nothing on these pages can be served from a stored copy, so the cost of every request is the cost of WordPress itself.
- A persistent object cache (Redis or Memcached) keeps query results between requests and is the single biggest win for logged-in and checkout traffic. It needs host support and a drop-in; the layer model is in choosing a cache plugin.
- A current PHP version. Each recent release has been measurably faster; test on staging.
- Fewer plugins on checkout. Every active plugin loads on checkout whether it is used there or not. Upsell, analytics and marketing plugins often add their own scripts and AJAX calls to the most sensitive page you have.
- Payment gateway scripts. Load only the gateways you offer; each one brings its own JavaScript.
- Hosting. Shared plans that throttle CPU show up as random slow checkouts rather than consistently slow pages. If uncached TTFB is above a second with the object cache on, the plan is the limit — hosting for WordPress covers what to look for.
If checkout is not just slow but failing, that is a different job: WooCommerce checkout not working.
The database and background jobs
A store accumulates data a blog never does.
- HPOS. High-Performance Order Storage stores orders in dedicated tables instead of
wp_posts. New stores use it by default. Older stores migrate under WooCommerce → Settings → Advanced → Features: enable compatibility mode, let it sync, switch to HPOS, then turn sync off and remove the legacy copies withwp wc hpos cleanup all— after testing every order-related plugin on staging. - Action Scheduler. WooCommerce and its extensions queue background jobs here. Completed actions are kept for 30 days and failed ones indefinitely, so a busy store can hold hundreds of thousands of rows.
- Sessions and transients. Expired sessions are cleaned twice a day by cron; if WP-Cron is not firing, they are not.
Check the size of each:
SELECT status, COUNT(*) FROM wp_actionscheduler_actions GROUP BY status;
SELECT COUNT(*) FROM wp_woocommerce_sessions;
SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '\_transient\_%';
A large pending count means jobs are not running. A large complete count means cleanup is not keeping up: completed actions are kept for 30 days and purged in small batches while the queue runs. failed actions are never purged automatically, so a growing failed count means a job keeps erroring — Tools → Scheduled Actions → Failed shows which hook. Pending and complete backlogs both point at cron — how WP-Cron works explains why it misses on low-traffic and cached sites.
Speeding up the WooCommerce admin
The admin is never page-cached. A slow orders screen is usually the order storage (migrate to HPOS), Action Scheduler bloat, or an extension running reports on every admin load. Disable extensions one at a time on staging and time the orders screen after each.
Common mistakes
- Caching the cart or checkout, then debugging empty carts and orders placed with the wrong totals.
- Two page caches, one in the host and one in a plugin, fighting over the same pages.
- Judging the store by the homepage. It is the fastest page you have; measure checkout too.
- Deleting Action Scheduler rows with raw SQL while jobs are pending, losing renewals or emails.
- Adding a filter plugin that runs unindexed meta queries on every category page.
Verify
Load a product page twice logged out and confirm a cache hit in the response headers. Add a product to the cart and confirm the next page shows it, and that a second private window does not. Then time the checkout with the object cache on and off — the difference tells you whether the server is the remaining bottleneck.
Frequently asked
- Any page-cache plugin that excludes cart, checkout and account pages and respects the WooCommerce cart and session cookies. Page caching only helps the catalogue; for logged-in customers and checkout, a persistent object cache matters more than the plugin choice.
- Because a store adds pages that cannot be cached, a session per visitor, background jobs in Action Scheduler and heavier product queries. The uncached half runs PHP and the database on every request, so hosting and the database decide its speed.
- It stops the header cart count updating without a page reload on pages where you disable it. Disable it only on pages without a live cart count, or everywhere if you accept the count updating on the next page load, and test adding to cart from a product page.