WordPress and WooCommerce Performance Optimization. Part 1

Andrii Toniievych
5 min readJan 1, 2024

--

Let’s discuss how to make WordPress and WooCommerce work faster on big projects with thousands of products, millions of orders, and tons of other data.

The website performance consists of two essential parts:

  1. Decrease the page generation time
  2. Optimize scripts, styles, and images

In this article, I will cover only the first part. Optimization of scripts, styles, and images is covered in the second part.

The page generation time defines the delay between the initial request and the response from the server. A browser has to get the HTML code before loading styles, scripts, images, and other assets. It will be always there on every request:

You can see this delay using Chrome Developer Tools, for example

There are a few ways to improve the page generation time:

  1. Full-page caching
  2. Object caching
  3. Database optimization
  4. Code optimization

Let’s start with the full-page caching. It’s the easiest way to improve the performance. The system generates a page once, then saves it in the cache, and loads the page from there instead of generating it every time.

This approach works well for guest users getting the same page content. Many hosting providers offer this type of caching. Also, it can be implemented using various plugins like WP Rocket and even services like Cloudflare.

Unfortunately, full-page caching does not work if the content should be different depending on a user account, location, cart contents, etc. In this case, you must implement workarounds, like turning off the cache for logged-in users.

Here’s another example: WooCommerce supports a feature called cart fragments. The cart content is requested using a separate request, saved in the browser storage, and loaded on every page load. It allows full-page caching to be used with WooCommerce stores.

Also, it’s possible to use separate caches for different countries, etc., but this approach is inefficient and rarely used.

The full-page cache is not a silver bullet; we must find other ways to make the code work faster.

The most frequent bottleneck on medium and large projects is the database. Queries over the meta fields, posts, order details, and other large tables may take too long.

The Query Monitor plugin offers the easiest way to see the problems. It helps to see the slow queries and other valuable details. It makes sense to use the Xdebug profiler in more complex cases, but it’s a topic for another article.

The most obvious first step is to enable the object caching. For some reason, it’s not as popular as full-page caching I described before.

Instead of querying and processing some data from the database, the system can load it from a much faster object cache. A lot of plugins and WordPress by itself support this type of caching. In most cases, hosting providers offer Redis as a data storage. Memcached and APCu are way less popular.

Here’s a quick example of how useful object caching can be. I took a medium-sized WooCommerce store with ~100K of products and images. The home page requires 245 database requests without and 1 (one) request with object caching:

The object cache is disabled, 245 database requests
The object cache is enabled, and one database request

It will allow a store to avoid significant performance issues for years because the database load is low.

Usually, the object caching plugin does not require additional configuration, but sometimes, you may need to specify the connection details and other tweaks.

The most crucial part of the object cache setup is the actual latency. It should be around 25–500μs (microseconds). If the latency is higher than the numbers above, the performance boost of object caching may be way smaller.

You can see latency values using the Object Cache Pro plugin:

The object cache latency value should be between 25–500μs

Usually, once the object cache is enabled and configured, you will see a massive drop in the number of database queries.

In addition to object caching, it’s essential to improve the database performance. There are three ways to do it:

  1. Configure database indexes for frequently queried data. Usually, I add a few additional indexes to the postmeta table with a column length limit.
  2. Simplify database queries, removing joins over large tables and adding limits.
  3. If the database is on a separate server, ensure the network latency is low. Even 20–30ms latency can destroy your performance. In this case, consider moving the database to the same server or at least the same data center.

Once the database is optimized, it makes sense to optimize the code. It’s the most complicated part and heavily depends on the actual code.

Here are the most common approaches on how to do that:

  1. Preload the required data and store it in a memory.
  2. Switch to the database queries instead of using the WordPress API.
  3. If you don’t need some data, do not query it.

Let’s take a long product list as an example. Each product card has metadata, terms, categories, and other information. If we use the out-of-the-box approach, the system will make a few caching requests per product to get all the metadata. terms, attachments, etc. It’s not bad, but it requires time and memory to process the data.

We can improve it by preloading the data as a memory map (a global PHP array) and getting it by ID.

For example, some products have a custom meta field with volume. We can make one request to the database to build an array with Product ID => Volume, store it in the cache, and get the volume of a product by accessing this array. You can see how it can be implemented in my theme framework.

The more products we have on the page, the more significant improvement we will see. It’s highly efficient for processing thousands of products.

At the same time, we must use this approach wisely because, on massive arrays, we can experience the object caching decompression delay and increased memory usage. No need to have the whole database in an array 😜

Something similar can be done to meta fields, terms, and post data. My favorite usage is building a map with Post ID => Post Title and Term ID => Term Title. You may find some code here.

In the next article, I will describe how to optimize scripts, styles, and images.

If you like articles like this one, please like and subscribe on my LinkedIn and Twitter.

Thank you!

--

--

Andrii Toniievych

Senior WordPress & WooCommerce Developer and Tech Enthusiast