A customer picks a product, adds it to the cart, clicks through to checkout — and then waits. The spinner on the payment step turns for six or seven seconds. Some of them wait. Most of them do not. The order never appears, so it never shows up in any report, and the only trace is a conversion rate that quietly sits below where it should be.
The usual response is to install a caching plugin. On a WooCommerce checkout that does almost nothing, because the checkout page is one of the few pages on a store that must never be cached. Whatever is costing those seconds is still there afterwards.
What follows is the order we work through when a store owner sends us a slow checkout, and what each step usually turns out to be worth in milliseconds.
The PageSpeed score is measuring the wrong page
Almost every store that contacts us about checkout speed opens with a PageSpeed Insights score for the home page. It is the wrong measurement twice over.
The home page is cacheable, so it is served as a flat file and looks fast. The checkout is not, so every request runs PHP and hits the database. A store can score 96 on its home page and take seven seconds to render a checkout. The two numbers are not related.
Measure the checkout itself, with something in the cart, while logged out. In Chrome DevTools, open the Network panel, tick Disable cache, load the checkout and read two things: the time to first byte on the document request, and the point at which the payment fields become usable. Those are the numbers that decide whether an order happens.
Where the seconds actually go
The waterfall above is from a real store — a 412-product catalogue on mid-tier shared hosting, 6.8 seconds to an interactive checkout. It is representative rather than unusual. The important thing about it is that no single cause accounts for the delay. Five of them stack, and the largest is rarely the one the owner suspects.
Here is each one, in the order they are usually worth fixing.
Cause 1: the checkout runs uncached, every single time
WooCommerce excludes cart, checkout and account pages from page caching, and it is right to. Cached checkout pages leak one customer’s cart to the next visitor. So every checkout load is a full PHP execution: bootstrap WordPress, load every active plugin, build the session, calculate totals, render.
That figure — 2,140 ms in the example — is your real server speed with your real plugin stack. It is what visitors would experience everywhere if caching were switched off. Two things move it:
- Object caching. Redis or Memcached keeps the results of repeated database queries in memory between requests. On a plugin-heavy store this is routinely worth 400–900 ms on its own, and most shared hosts either offer it or will enable it on request.
- The PHP version. A store still running PHP 7.4 is leaving roughly 20–30 per cent of its execution time on the table against PHP 8.2. This is free, and it is the first thing to check.
What does not move it: upgrading to a bigger server. A slow uncached page is usually slow because of what the code is doing, not because the CPU is saturated. Doubling the hardware on a 2.1-second checkout typically buys 200 ms and a larger invoice.
Cause 2: cart fragments, running on pages with no cart
WooCommerce ships a feature called cart fragments. It is what lets a cart icon in the header update its count without a page reload. It works by firing an admin-ajax request on page load — and by default, on every page load, including pages with no cart icon on them at all.
That request is uncached by definition. On the store above it cost 1,380 ms, and it was firing on the home page, every product page, every blog post and the contact page. It is the single most common piece of avoidable weight on a WooCommerce site.
The fix is not to disable it globally, which is the advice usually given and which breaks the cart counter. It is to dequeue it on the pages that have no cart interaction, and leave it running on the shop, product, cart and checkout pages where it does real work. That is ten lines in a child theme, and it is the cheapest large win on this list.
Cause 3: shipping rates calculated live, in the request
If your store quotes live rates from a carrier — UPS, FedEx, Royal Mail, Australia Post — then every time the customer changes country, state or postcode, WooCommerce makes an outbound API call and waits for the answer before it can render the totals.
You are now dependent on someone else’s server. When the carrier API is having a slow morning, your checkout is having a slow morning, and nothing in your own stack will show why. In the example above this was 1,720 ms — the second largest cost, and entirely outside the store’s control.
Three things help, in descending order of how much we like them:
- Cache the rate response against the destination and cart weight, with a short expiry. Two customers in the same postcode ordering the same weight get one API call between them, not two.
- Set a hard timeout on the carrier request and fall back to a flat rate when it is exceeded. A checkout that quotes a slightly wrong shipping price is worth more than a checkout that hangs.
- Consider whether live rates earn their cost at all. For a store shipping mostly domestic parcels in three weight bands, a table rate is more accurate in practice and costs nothing.
Cause 4: payment gateway scripts loading site-wide
Stripe, PayPal and Klarna each load a JavaScript SDK from their own domain. Several of them enqueue it on every page of the site rather than only where a payment can happen, and PayPal in particular is prone to this.
These are third-party scripts on third-party domains, which means a fresh DNS lookup, TLS handshake and download that your own caching and CDN cannot touch. In the waterfall it is 940 ms.
There is a limit to what can be done here. The gateway’s script has to load on the checkout, and it has to load before the payment fields work — that portion is not negotiable and PCI compliance depends on it. What can be removed is the copy loading on your blog and your contact page. Half of this figure is usually recoverable; the other half is the cost of accepting card payments.
Cause 5: the database, once the catalogue grows
WooCommerce stores product data across wp_posts and wp_postmeta, and variable products multiply the rows fast. A product with four sizes and three colours is not one product — it is twelve variations, each with its own meta rows for price, stock, SKU and weight.
Install Query Monitor, load the checkout while logged in as an administrator, and look at the query count. Under 100 is healthy. Between 100 and 250 is worth attention. Above 400 there is something specific and findable going wrong — usually a plugin running an unindexed meta query inside a loop.
If your store is still on legacy post storage, moving to WooCommerce’s High-Performance Order Storage is the structural fix. It puts orders in their own tables instead of the posts table, and on a store with tens of thousands of orders the difference in admin and checkout query time is not subtle. It needs testing against every extension you run first — some older plugins still assume the old tables.
The order to work in
Measure first, in the checkout, logged out, with a cart. Then work down: PHP version, object cache, cart fragments, shipping rates, gateway scripts, query count. Re-measure after each one rather than doing all six and hoping.
The store in the waterfall went from 6.8 seconds to 1.9 on the same host, with the same payment gateway and no theme change. Nothing on that list is exotic. It is just done in order, with a measurement between each step, which is the part that gets skipped.
What this cannot fix
Speed work has a ceiling, and it is worth being clear about where it sits.
- A fast checkout does not fix a checkout that asks for too much. If you require account registration before payment, or run a five-step flow where two would do, the abandonment is a design problem and no amount of milliseconds will touch it.
- It does not fix trust. Customers abandon carts because the shipping cost appeared late, the returns policy was not findable, or the site did not look like somewhere to type a card number.
- It does not fix a gateway that is failing. If payments error rather than merely lag, that is an integration fault and speed work will make it fail faster.
- It has a floor. A checkout that must run PHP, hit a database, call a carrier and load a payment SDK is not going to serve in 200 ms. Somewhere between 1 and 2 seconds is a realistic target on shared hosting, and anyone promising better is measuring a cached page.
Questions we get asked
Why is my WooCommerce checkout slow when the rest of the site is fast?
Because the rest of the site is cached and the checkout cannot be. Home pages, product pages and blog posts are served as flat files by your caching plugin or CDN. Cart, checkout and account pages are excluded from that by WooCommerce, so they execute PHP and query the database on every request. The gap between the two numbers is your real server and plugin cost, which caching had been hiding everywhere else.
Will a faster host fix a slow checkout?
Partly, and usually less than expected. Moving from oversold shared hosting to a properly resourced host with PHP 8.2 and Redis available is worth real time — often 400 to 900 ms. But if the checkout is slow because cart fragments fire on every page and a carrier API is being called synchronously, those costs move to the new host intact. We would measure before recommending a migration, not after.
Should I disable cart fragments?
Not globally. The advice circulating on forums is to dequeue wc-cart-fragments site-wide, which does make things faster and also stops the cart counter in your header updating until the page is reloaded. The correct version is to dequeue it everywhere except the shop, product, cart and checkout pages. Customers keep the live counter where it matters and every other page stops making an uncached AJAX call it had no use for.
How many database queries should a WooCommerce page make?
Under 100 on a product or checkout page is healthy for a store of any size. 100 to 250 is common and usually improvable. Above 400 there is nearly always one identifiable cause — a plugin running a meta query inside a product loop, a slider pulling every product to display six, or a currency switcher recalculating per row. The count matters less than the shape: one slow query is easier to fix than four hundred fast ones.
Is High-Performance Order Storage worth switching to?
On a store with more than a few thousand orders, yes — it moves orders out of the shared posts table into dedicated tables with proper indexes, and admin order screens stop crawling. The caveat is compatibility: any extension that reads orders directly has to support it, and a few older ones still do not. It is a change to make on a staging copy with every extension active, not on a live store on a Friday.
What does it cost to have this done?
Speed optimization starts at $349 and covers the measurement, the profiling and the fixes that come out of it. Where the checkout needs structural work — a rebuilt payment step, a shipping integration reworked, an HPOS move tested against your extensions — that is WooCommerce development, from $1,200. Which one applies is exactly what the free audit tells you, and it is written down before anyone quotes.

