Performance, caching and launch
A shop that worked well with ten products on a local machine meets real load on a production server: hundreds of orders, a cache, mail, a payment service. Three things are checked before launch: that the cache does not break the cart, that your code does not slow pages down and that background jobs run.
What must not be cached
A page cache serves saved HTML instead of running PHP. The catalog and product pages can be sped up that way, but personal pages cannot: one customer would see another's cart.
| Do not cache | Why |
|---|---|
/cart/, /checkout/, /my-account/ | The content is different for every customer |
Addresses with ?add-to-cart= and wc-ajax= | They change the cart |
/wp-json/wc/store/ | The block cart's data |
Requests with the cookies woocommerce_items_in_cart, woocommerce_cart_hash, wp_woocommerce_session_... | The visitor has a cart |
Popular cache plugins know these rules themselves. If the cache is set up on the server or in a CDN, the exclusions are added by hand.
Your code and speed
Four habits that keep a plugin from slowing the shop:
| Habit | Example |
|---|---|
| Limit queries | limit => 12 instead of -1 |
| Take IDs when objects are not needed | return => ids |
| Make no external requests while rendering a page | Move them to Action Scheduler |
| Remember heavy results | A transient for an hour |
Remembering a result
A transient is a value saved in the database for a while. Let us take the featured products list out of every request:
// wp-content/plugins/acme-shop/includes/products.php
function acme_featured_ids(): array
{
$ids = get_transient('acme_featured_ids');
if ($ids === false) {
$ids = wc_get_products(['featured' => true, 'limit' => 8, 'return' => 'ids']);
set_transient('acme_featured_ids', $ids, HOUR_IN_SECONDS);
}
return $ids;
}
add_action('woocommerce_update_product', function () {
delete_transient('acme_featured_ids');
});The first call runs the query and saves the result for an hour; the following ones take the ready array. The woocommerce_update_product hook drops the saved value when any product is changed — the list does not go stale. If the server has Redis or Memcached with an object cache plugin, transients are kept in memory.
The system scheduler
WordPress background jobs are triggered by site visits: no visitors means no emails and no exports. On a production server this is replaced with the system scheduler. Add define('DISABLE_WP_CRON', true); to wp-config.php and a run every minute to the server scheduler:
* * * * * cd /var/www/shop && wp cron event run --due-now --quietThe command runs all due jobs, including the Action Scheduler queue.
Before launch
| What to check | Where |
|---|---|
| HTTPS on the whole site | The site address in Settings → General starts with https |
| Shop pages are assigned | WooCommerce → Settings → Advanced |
| A test order with a real payment | The payment goes through and the bank's notification arrives |
| Emails are delivered | Sending through SMTP, the email is not in spam |
| HPOS is on | WooCommerce → Settings → Advanced → Features |
| No outdated templates | WooCommerce → Status, the Templates section |
| No failed jobs | WooCommerce → Status → Scheduled Actions |
| Debugging is off | WP_DEBUG is false in wp-config.php |
| Backups | Scheduled, and a restore has been tested |
The WooCommerce → Status page gathers most of this list: versions, settings, warnings in red.
Updates
WooCommerce is updated often. First update a copy of the shop on a test server, walk the path from the catalog to payment and only then update the production site. After an update run wp wc update from the console to update the database tables.
Common mistake
A full page cache is turned on for the production site for speed, and the cart is not checked as a second visitor. The first customer adds a product, the cart page gets into the cache, and everyone else sees someone else's cart or an empty one instead of their own. After setting up the cache open the shop in two different browsers and make sure the carts are independent.