WooCommerce: Can You Actually Get a Speedy Store?
Woo that flies - even for logged-in users. Sounds impossible, right?
Before we dive into another cracking WordPress adventure, let’s take a short break for a story. A former client of mine had a top class Woo site that was bringing in sales like clockwork - to the tune of, at times, 2-3 per second. They weren’t happy with the site speed though and had decided to build their own, using Next.js and Shopify APIs. I was brought in to steady the ship before the transition. We steadied it … but not enough. It was never truly rapid, especially if you were a logged in customer. My skills have come on since those days, and often times I can’t help but wonder - What If?
So, let’s try to answer that question. Can we get a Woo site to such a lick of speed that Shopify dev teams will look on in envy?
Step 1: Install the basics & test
We need to try to get this realistic. Every client Woo site I’ve worked on has had a lot of plugins. A ton, in fact. Many WooCommerce extras for payment gateways, taxes, memberships, subscriptions … then plugins for fraud checking and mitigation and don’t forget reviews and customer follow-ups. These are great, fine. Essential for a world-class ecommerce site. But they’re bad news. And they help contribute to PageSpeed scores you’d be embarrassed to take home to meet your mother. Ones like this:
Not pretty! (this was the site I mentioned earlier, before the ship steadying).
So, if we’re to have a realistic test we’ll need to simulate a site slowed down by plugin load. So, we’ll install the following:
WooCommerce
WooCommerce Smooth Generator (to generate products)
Site Slowdown (a plugin to add a 1 second delay to every page load)
The basic Twenty Twenty-Four theme that comes with WordPress
So, let’s see what we get…
I’m running this locally this time because we’ll need to test logged in users later.
So, the score is pretty much as expected. We have a nearly 2 second initial server response (due to the simulated delay), and we have the usual issues with LCP, JavaScript, and CSS.
Once loaded, using the page feels fine though! When you click a button to add to cart it feels like it happens instantly, even though the AJAX requests take over a second. This is because the new Woo Blocks handle things with React and do the updates in the background.
So … there’s potential.
Step 2: Turn on the optimisations!
So, we’ll start with the images as usual.
Optimise images: I installed CompressX to handle this and ran it once.
SpeedifyPress Plugin: So, this is the part where I run SpeedifyPress, the performance plugin that I have developed. It handles pretty much everything else!
Remove Unused CSS: Woo ships with a lot of CSS. 23 different files, to be precise. Let’s remove the CSS that isn’t being used.
Delay JS: Woo ships even more JavaScript. We have all the Woo JS, the blocks JS, the react JS. It’s loaded cleverly, but it’s still there render blocking … so we should delay it.
Lazy load Images: we’ll add a preloader SVG and lazy load the images
Font Options: I enabled all the options here, ensuring required fonts are preloaded (and system fonts used on mobile)
Caching: for the moment, I just enabled the standard caching
Cloudflare: I added the Cloudflare worker to the site for improved Edge caching and document compression (with zstd enabled)
Let’s see what that gets us…
OK, that’s pretty decent. There’s a small issue with the LCP, which seems to be differing between desktop and mobile. But we can leave that for the moment.
The page loads extremely quickly, and the Site Slowdown plugin isn’t affecting things as it’s now sitting behind the Cloudflare cache:
But wait! We still have problems…
The CSS is all over the place, there are lots of selectors it didn’t capture
The JavaScript isn’t working properly
If I add an item to the cart, I lose the Cloudflare caching
If I’m a logged in customer, I lose all caching (and hit the 1 second site delay)
So, let’s move on to…
Step 3: Fix all the issues
The CSS
So , what’s happened to the CSS? Well, the site has lots of selectors that are added to the page via JavaScript. SpeedifyPress will capture some of these … but not all. So it comes down to a good old manual job. View the page with the borked CSS, view the page with the good CSS, compare, contrast and find the missing selectors. This process took … a while, across the different pages of the site. But an hour or so later I had the final list and the CSS was perfect across the site:
wc-block
loading
is-loading
screen-reader-text
stars
-open
modal
wp-block-navigation
The JavaScript
The JavaScript implementation is really interesting here for this WP Blocks site. There are lots of JS files, and there’s react going on to update the cart buttons and various other things. It’s all elegantly preloaded too, to prevent render blocking. So, not delaying the JS here makes almost no difference (only jquery blocks render, and it still scores a 99).
However, on a real site there’s bound to be plenty of other JS getting loaded in from all those plugins we mentioned before. So … let’s see if we can delay-load the JavaScript anyway.
I’ll just set the default delay settings (with js-extra excluded as standard) and see what happens…
The Results … and the fixes
There are issues with the JS delay, some buttons not working properly, some errors in the console. But it’s nothing that can’t be fixed. Here are the fixes:
Set view.min.js and index.min.js to load LAST. These are the two scripts that were printing errors into the console and loading such scripts last often helps with this.
Set a function using wp.data to load last (once wp.data is available)
Set a function using wp.date to load last (again, once wp.date is available)
That fixed everything. Almost. With view.min.js and index.min.js loading last, the hamburger menu no longer works. I couldn’t find any way around this, so I created a quick script to replicate the functionality of that modal and added it into the head. Often, this is good to do anyway when you’re delaying JS because that hamburger menu is often clicked before the JS is loaded, so you need some extra plumbing to handle that.
The Cloudflare Caching
By default the worker that ships with SpeedifyPress will not cache WooCommerce pages. This is because often themes are poorly setup for that. They may hardcode the cart onto the page, for example. But, if you’ve got a theme that doesn’t (like we do here), we can remove that restriction. To do so, I just hop into the worker script and remove woocommerce_ from the list of bypass cookies. Easy!
Now we have the benefit of Edge caching and lazy cache preload. This means our pages will run nice and fast even after a cache clearance. This is great for WooCommerce and its product filter URLs like /?filter_color=green%2Corange&query_type_color=or that we really want to run instantly.
Logged in customers
This is a big one. Logged-in users can really slow down a Woo site as, often, they won’t see cached pages. On some sites, just adding an item to the cart will stop caching too. We want to stop all that.
And, just for fun, let’s see if we can cache the /cart and /checkout pages too.
So, I’ll enable the logged in user caching and set some options as follows:
So, I’ll only cache logged in users on the homepage, product pages, cart and checkout. And I’ll exclude the WP Admin bar from being cached so admin users won’t see each others admin bars. Note the cache creates separate caches per user role (so users and admins are never mixed).
The results (and fixes!)
This works surprisingly well! The Blocks use a skeleton loader for the cart and the checkout, so there’s no hard coded info there that we need to exclude. Apart from … on the checkout. There the cart is hardcoded into the settings with the wcSettings variable! So when you go to checkout, you can see a different user’s cart. Not ideal.
To fix, we can’t just remove the wcSettings object, which I did try initially (using Find/Replace). Instead, we can define the object early so the settings are available BUT the wp.apiFetch won’t run to bring in the hardcoded settings.
We then just need to add in a quick function to prevent the lack of apiFetch from erroring out (until it’s added later on)
Yes, it’s complicated! But I do enjoy these challenges.
So, we’re done … right?
Not quite! We still have a nonce problem!
What’s happening here? Well the checkout stores the nonce hardcoded onto the page in the wcBlocksMiddlewareConfig variable. This means when we cache for logged-in users, two different customers would be given the same nonce.
So … the best option is to follow the recommendations and not cache checkout pages. UNLESS … you’re doing it for a bit of fun like we are. In which case, we can come up with a workaround and use Find/Replace in SpeedifyPress to implement it:
Voila! No more nonce issues on the Checkout.
So we’re done? Still no! WooCommerce does even more hardcoding…
The shop page hard codes the amount of products in the cart into the “Add to Cart” button
The shop page stores a lot of data into the “wp-script-module-data-@wordpress/interactivity” config script. Including the nonce!
So to fix this we can use an element Find/Replace to fix the Add to cart button
And we can use some custom JS to overwrite the hardcoded nonce:
Step 4: Give it a test!
The site is available online here:
https://woo.speedifypress.com/
The shop page should pop in like lightning. Click a few filters - should be almost no delay at all. Click onto the cart page - instaload! (before the cart items load in, that’s still subject to the forced 1 second delay plugin). But the important thing is the user feels it’s snappy. Go back to the shop page (instaload), add an item to the cart (instant feedback on that action), click the cart button in the header (instantly shown). Now click on Checkout - the page will load in immediately.
But what’s it like for logged in users? (those poor, forgotten actual customers)
Well, they’re finally getting a speedy shopping experience.
Note that clicking “Add to Cart” on the product page is not cached, and feels slow. The rest of the pages snap in. Cart and checkout need a skeleton loader, but it still feel quick to the user.
How to optimise your own site
First off, this is advanced stuff! I have had to “hack” this to get it to work. I wouldn’t recommend caching the cart or checkout pages on production. But if you really want world class loads on those pages, I’d recommend building out your own templates for them that are more robust.
With the shop page … it’s a pity that Woo is hardcoding cart amounts and nonces onto the page. It makes things tricky. You can use the hacks that I have or write your own template. Or, use a pro template that has already sorted out these issues. My next task is to find one and test it!
Of course, for all the caching and optimisation SpeedifyPress is on hand to help. If you’d like access, you can subscribe below for a free single site license: