Learn how to diagnose a slow WooCommerce site. Instead of making a “best guess” or simply deactivating plugins, we’ll outline a process that identifies exact problems and addresses them. Although there’s no “one fix” for all slow WooCommerce websites, this process will help you optimise any WordPress/WooCommerce site.
TIP: I recommend you read this post start-to-finish before you try and apply any of it to your WooCommerce site.
Start at the beginning
Before we start trying to figure out why the page-load is slow, we need to understand how the page-load works, from start to finish. This is important information for web designers, as well as developers.
The following sequence is laid out as pseudo-code (“false” code). It’s meant to demonstrate the flow of a sequence of events and states while being easy to read by non-developers.
When a user points their browser at a WordPress URL (e.g. https://power-plugins.com/)…
- The browser performs a DNS lookup for the website’s domain to get an IP address
- The browser sends a HTTP Request to the server’s IP address
- The web server software (Apache/Nginx/LiteSpeed) waits for the next available PHP worker to become available. If a PHP worker is not available within 30 seconds, the web server returns a timeout Bad Gateway error (502 or 504) to the browser and aborts the request
- The web server hands the request to the PHP worker
- If the HTML for this URL has already been cached then…
- Grab the HTML from the page cache and hand it back to the web server immediately
- Else…
- The PHP worker loads the WordPress PHP files, plugins, theme and runs the “init” action (and other actions too)
- Creates the main WP query
- Renders the HTML opening
html
tag and headers - Loops through the WP Query results and render the
body
HTML - Renders the HTML footer and close the outer
html
tag - Hands back control to the web server
- If the HTML for this URL has already been cached then…
- The web server returns the HTML to the browser, along with an HTTP Response code (200, if everything was good, 404 if the URL is not found, etc)
- The browser processes the HTML to look for URLs (scripts, CSS, images) and requests these assets from the web server. This will involve additional DNS lookups if assets are on alternative servers (e.g. fonts.google.com)
- The browser starts to execute JavaScript
- Some JavaScript might load additional JavaScript and CSS from external sites.
- When the browser has processed the HTML to create the DOM, it triggers the “loaded” event
- Some JavaScript code will make additional POST requests to wp-admin/admin-ajax.php. For each of these requests…
- The web server waits for the next available PHP worker
- The web server hands the request (and the POST data) to the PHP worker. If no worker becomes available within 30 seconds, the web server returns a timeout Bad Gateway timeout error to the browser and aborts
- The PHP worker loads WordPress, plugins, theme and runs the “init” action
- The PHP worker executes the required Ajax action handler function
- Render the JSON response
- Hand back to the web server
- The web server sends the JSON response back to the browser with the response code (e.g. 200 OK)
- The page has finally loaded
The pinch points are when something is waiting. We can see a few points here:
- Waiting for DNS lookups to come back with an IP address
- Waiting for a PHP worker to become available to process the initial HTML request
- Waiting for additional DNS lookups from external JavaScript/CSS assets
- Waiting PHP workers to become available for the additional Ajax call-back(s)
Any WooCommerce site’s front-end will use at least two PHP workers. The first will return the HTML (which should be fast, if the page caching is working) and the second will return the user’s current cart items. This second call is an Ajax callback which runs after some JavaScript has loaded – that’s why there’s a delay between the page loading and the number-of-items badge showing.
JavaScript that manipulates the DOM (changes the position of elements) and/or downloads JavaScript from external sites (which has to execute) can cause all sorts of user experience (UX) problems, with DNS delays and slow server-responses that are out of your direct control.
So we need to make sure that…
- PHP code on our server executes quickly
- A page-load doesn’t need too many PHP workers (not too many Ajax call-backs)
- There are not too many JavaScript assets “doing stuff” in the browser that we’re not aware of
Measure your page speed
The page-load waterfall is an easy way to see what’s loading quickly, and what’s not. If your page caching is working normally, the first request/response (for the page HTML) should be fast – around 100ms (a tenth of a second). If you’re seeing response times over 1 second, either your page caching is broken or you’re testing the site from a location that’s on the wrong side of the world. For example, GTmetrix will default to testing sites from Vancouver in western Canada. That’s not much use if your website is hosted in London and most of your customers are in Europe.
You can run a waterfall analysis in most browsers, but I find the GTmetrix page-load waterfall is easier to work through.
Here’s a scan of the dev site I use to build+test my projects:
Key points
- The initial request/response is the first line. Because we’re testing the front page, the URL path is simply “/”. The complete request/response time is 104ms, which is fine.
- At the bottom of the list there’s a grey footer line with summary information. This shows the complete start-to-finish for the entire page is 1.9s. That’s on the slow side, but the page does contain an MP4 video.
- Our page-load consists of 39 requests, including all the CSS/JS and images. That’s about right. If your page-load has over 75 requests, you might want to try and reduce the number of assets you’re serving. If a lot of those requests are for CSS & JS assets, you can use a plugin to aggregate these into fewer requests.
- Some of the JPGs are taking a long time to load.
- Scroll down the list and look for the record that gets the refreshed cart fragments. This is the WooCommerce Ajax call that slows down a lot of Woo sites. On this page-load it’s 441ms – not too bad.
From this analysis, it looks like a CDN would be a good step forward – that would fix the JPEG & MP4 issues. I like to use BunnyNet for asset-offloading like this. They’re great value and it’s quite easy to get a good performance boost. You could use a free Cloudflare account, but configuring that is quite a technical process.
If your site has multiple records that start with “POST…” (sending data to your server and executing PHP code) you will bottleneck your PHP workers. For example… if your page-load makes 4 Ajax POST callbacks, you’re actually using 5 PHP workers in total. If your hosting configuration only has 2 PHP workers available, some of these callbacks will need to wait for the previous callbacks to finish. For a very small site, that might be OK. But if you’re logged-in to the admin area while you’re running a social media campaign, you will bottleneck the site immediately and it will feel very very slow (or you will see timeout errors).
TIP: Ask your hosting provider how many PHP workers are allocated to your site.
TIP: When you run campaigns that generate high bursts of traffic, log out of your site’s admin area to free up as many PHP workers as possible for your customers.
Diagnose slow PHP performance
If you’ve got problems with PHP requests taking a long time to execute, we need to pick apart WordPress itself to see where the problem is (or where the problems are).
The two main causes of slow PHP code:
- having lots of database queries on a page
- a plugin is making outbound HTTP API calls for some reason (perhaps to check for licence validity).
A great tool for analysing what WordPress is doing is the Query Monitor plugin (free).
TIP: Query Monitor adds a memory overhead to database queries, which can cause problems if you run large WP/Woo imports or exports. It’s advisable to only activate Query Monitor when you’re diagnosing a problem, and deactivate it when you’ve finished.
TIP: Recent versions of PHP are noticeably faster than older versions. So always make sure PHP is up to date. As of September 2024, you should be running PHP 8.3.
Check for excessive or slow database queries
On my dev site’s main “shop” page, there’s a total of 264 database queries. Most of these are from WooCommerce (to be expected) and GeneratePress (my base theme). No group of queries takes a particularly long time to execute, and all 264 queries executed in just over 0.015 seconds. So the database interactions look OK here.
On troubled sites, you might see over 500 database queries on a single page-load. That’s way too many, so you can see which plugin is misbehaving and raise a ticket with the developer – ask them to reduce the number of DB calls.
TIP: Using an object cache can reduce the number of database calls with well written plugins. So make sure your site is using an “object cache” such as Redis or Memcached. Check with your hosting provider to see which object cache they use. There’s no point in installing a Memecached object cache plugin if your hosting provider uses Redis, and vice-versa.
Check for outbound HTTP API calls
Query Monitor can be used in the admin area too, so if you go to your site’s admin dashboard and bring up the Query Monitor panel, you can see WordPress checking for updates.
This is the main cause of the admin dashboard page loading slowly. My dev site is quite light, but we still have to wait around 1.7 seconds while WP and Gravity Forms make API calls to their servers. In this case, all the API calls returned HTTP Response 200 (OK) so we’re in good shape. But if you have a misbehaving plugin, you might see it try to make an API call, then timeout after 3 seconds and fail. If you can remove that API call, your page will load 3 seconds faster.
The trouble here is that we often see old or poorly maintained plugins fail an API call because the developer has let their source domain lapse. Or perhaps they’ve changed from one domain to another without updating the plugin. If you encounter this and the developer is unresponsive, your only choice is to modify the plugin’s code and remove the API call yourself. It’s not difficult – any PHP developer should be able to help if you don’t want to hack the code yourself.
TIP: If you can’t find a WP/PHP developer, try reaching out to your hosting provider. If you use a proper Managed WordPress Hosting provider, they will probably be able to help you out.
Fixing JavaScript in the browser
TIP: Before we analyse JavaScript and CSS assets in the browser, we need to make sure we’re not running an asset aggregator like Autoptimize. If you’re aggregating your assets, deactivate that plugin while you run these tests and tweaks. You can reactivate your asset aggregator again afterwards.
Directly enqueued scripts
We’ll start by looking at the scripts in Query Monitor. These are the JavaScript files that WP/Woo directly enqueues. These scripts might fetch additional resources from elsewhere, but we’ll look at those later in GTmetrix.
Here’s the list of assets on my dev site’s “shop” page, while I’m logged-in as an administrator.
We need to look through this list and see if anything is out-of-place. Things to look for:
- Are any back-end scripts enqueued by mistake, like block-editor scripts or jquery modules such as the date/time picker? If so, look for which plugin has caused the scripts to be enqueued by checking the “Dependencies” column.
- If you’ve got more than 40-or-so scripts enqueued, look down the “Source” column to see if any one plugin is responsible.
- Look for scripts that aren’t needed on that particular page. Lots of plugin developers are “lazy” – they enqueue their plugin’s JS & CSS assets on every page load, even if they’re not needed. Forms plugins are notorious for this. Usually you can use a PHP snippet to “dequeue” the assets on pages where they’re not needed. Dequeue assets with the Contact Form 7 plugin.
In my experience, the thing that can really drag down a page-load is when a plugin enqueues a load of back-end scripts in the front-end for some reason. In those cases, we’ve had to go to the plugin developers and ask them to fix their code.
Scripts that load other scripts
This is a bit of a funny one, as there’s probably not be much we can do about it. But we’ll use the GTmetrix waterfall to see what’s what.
Look down the waterfall and you’ll see our directly enqueued scripts in there, with your website’s domain in the “Domain” column. If any of your scripts pull in additional JS/CSS from external sites, you’ll see something different in the “Domain” column.
If you find any scripts that load late and pull from external domains, they will be adding a big delay to your page-load time. Investigate if you can self-host those assets on your own site. That will let you bundle the assets with the directly enqueued script, and potentially aggregate & minify too.
Review aggregators don’t usually let you do this, but a lot of trackers/analytics will have instructions for self-hosting the assets on your site.
If you do have the option to self-host these assets, make sure you test the page-load from a non-logged-in browser window with the JS Console visible. If there are any errors with the self-hosted JS, you’ll see them in here.
Wrapping up
In short… don’t guess and start deactivating plugins randomly. Go back to the page-load sequence and figure out which stage is running slowly. If the problem is in the PHP, check for outbound HTTP API calls and excessive database queries. If the problem is in the browser, try to reduce the number of scripts you’re serving, change external scripts to self-hosted (if possible) and aggregate/minify your scripts.
Messing about with scripts can break things like your checkout page and contact form, so remember to test all primary functionality (contact us, pay now, analytics, etc) whenever you make changes that involve enqueuing/dequeuing scripts.
And make sure your hosting provider (and hosting package) are matched to your site’s requirement. If you’re trying to support 5 concurrent users and two administrators logged-in in the back-end all day, budget WP hosting will not work for you – your site will be slow or unusable.