WooCommerce store, 2,400 products: measured, stabilised, migrated

Sector
Fashion e-commerce, catalogue of about 2,400 products
Topics
Performance and Core Web Vitals
Situation
Shared hosting with no SSH, a stack frozen for months, products disappearing after every import and dynamic pages served in 1.8 to 2.3 seconds.
Outcomes
  • Origin TTFB from 954 to 636 ms on desktop (-33%) and from 1,027 to 647 ms on mobile (-37%)
  • LCP from 1,557 to 1,147 ms on desktop and from 1,488 to 1,055 ms on mobile
  • First corrected import 414 of 414, 3,417 orphan rows removed
  • 1,815,094 records verified one by one during the migration
Tooling
WordPress, WooCommerce, PHP, Playwright, sitespeed.io, Grafana, Redis, Cloudflare
Measurements before and after the WooCommerce migration

Clothing retailer on WordPress and WooCommerce, about 2,400 products and 22,000 variations, catalogue loaded every day from the company’s management system. Shared hosting without SSH or WP-CLI, stack not updated for months, products disappearing from the shop after each import, dynamic pages served in 1.8 to 2.3 seconds. The copy of the site I was handed still pointed at the production database with the payment gateway live.

Measure before touching

Full replica in an isolated local environment, every production check made directly against the origin bypassing the CDN cache, automated Playwright suite (36 tests at the start, 58 at the end) run before and after every change, nightly sitespeed.io measurements on a dedicated host feeding Grafana. When the owner reported a collapse in GA4 traffic, the measurement showed orders were stable: the consent banner was cutting the denominator, not the site. No work was done on a problem that did not exist.

Diagnose on evidence

Half of the import calls (101 of 207) were failing with HTTP 500. Four logs cross-referenced showed PHP processes being killed by the shared hosting, not a code defect, and the provider confirmed the limits could not be changed. That finding drove the hosting decision. At the checkout, a probe without personal data showed that the most frequent error (35%, unticked consent boxes) cost nothing because customers corrected themselves: the losses were elsewhere, and priorities were reordered before spending.

Fix at the source

Import rewritten so that products removed by the management system are hidden and kept, never deleted: first corrected run 414 of 414, zero failures, 3,417 orphan rows removed. Internal search extended with an Italian dictionary and a FULLTEXT index, no paid engine: searches with no result were 22.9% of queries, and the 45 most frequent now all return results. GTIN barcodes copied into the native WooCommerce field for 8,411 variants with zero duplicates. Order creation from 97.3 to 34.2 ms by removing a duplicate numbering plugin. The previous vendor’s 1,719-line plugin emptied in eight steps without editing it, each step compared across all 2,490 products and checked with a real order. Seven plugins removed, four replaced by small purpose-built code, 186 KB of JavaScript off the home page. Contrast errors on the home page from 91 to 1, measured with axe-core instead of installing an accessibility overlay.

Migrate with proof

Sixteen idempotent scripts, each rehearsed in a container before touching the server. The new server isolated until cutover. 1,815,094 database records verified one by one across 139 tables, and 413,586 files. DNS rollback rehearsed. Cutover with the shop closed and the old hosting kept intact as a safety net, final proof with a real low-value order.

Result at the origin, average of 10 pages: TTFB 954 to 636 ms on desktop (-33%) and 1,027 to 647 ms on mobile (-37%), LCP 1,557 to 1,147 ms on desktop (-26%) and 1,488 to 1,055 ms on mobile (-29%), Lighthouse mobile on the home page 61 to 75. Import on the new server: 188 records in 2 minutes 20 seconds, no process killed.

Limits

No load test was run on shared hosting during this phase: the contract required written authorisation and the engagement did not include it. CrUX field data were not part of this phase. The figures above are lab measurements against the origin, not field data. Published with the client’s consent.

Anonymised at the client’s request.