WooCommerce por defecto se atasca antes de que el catálogo empiece a ser interesante. Una instalación limpia con 200 SKUs vuela en staging, y la misma plantilla con 30k productos y 50k pedidos se mueve en TTFB de 1,5 a 3 segundos, los cart fragments serializan en el archivo de tienda y la fila autoload de wp_options cruza silenciosamente los 100k registros.
La plataforma no es el problema. WooCommerce mueve tiendas españolas con facturaciones de siete cifras anuales. Lo que mata el rendimiento es la configuración por defecto y la acumulación de plugins durante años.
Esta es una guía de campo para endurecer WooCommerce contra carga real, con apuntes específicos del mercado .es: integración con Bizum y Redsys, plugins de SEUR/MRW, hosting tipo Webempresa o Raiola, y el patrón de tráfico de campañas de Black Friday.
El problema del almacenamiento de pedidos y qué cambia HPOS
Durante una década WooCommerce guardaba pedidos como posts shop_order en wp_posts, con cada línea, dirección y campo de metadatos explotado en filas dentro de wp_postmeta. Una consulta tan simple como “pedidos de mayo” obligaba a escanear una tabla mezclada con revisiones de blog y borradores, y a hacer JOIN tres o cuatro veces contra postmeta para reconstruir cada pedido.
High Performance Order Storage traslada los pedidos a wc_orders, wc_order_addresses, wc_order_operational_data y wc_orders_meta. Las consultas de listado golpean columnas indexadas directamente en lugar de reconstruir cada fila desde postmeta.
Lo que HPOS no arregla solo:
- Plugins españoles que escriben directamente en claves postmeta como
_factura_serieo_albaran_seurse desincronizarán en silencio cuando desactives el modo compatibilidad - Plugins de facturación tipo WooCommerce PDF Invoices o conectores con Sage que leen pedidos con
get_post_meta($order_id, ...)en vez de$order->get_meta()devolverán vacío - Conectores ERP con Holded o A3 basados en
WP_Querycontrashop_ordernecesitan una reescritura
Flujo de trabajo:
- WooCommerce -> Ajustes -> Avanzado -> Funciones, activa High Performance Order Storage con modo compatibilidad
- Lanza la sincronización fuera de horas punta. En una tienda con 200k pedidos puede tardar varias horas
- Verifica con
wp wc cot verify_cot_data - Mantén la compatibilidad activa durante una semana mientras pruebas todos los plugins que tocan pedidos
- Desactiva la compatibilidad solo cuando hayas confirmado que cada plugin lee desde HPOS
wp wc cot verify_cot_data
wp wc cot sync --batch-size=500
Cart fragments y el peaje de admin-ajax
Pasa cualquier tienda WooCommerce por GTmetrix o WebPageTest y verás ?wc-ajax=get_refreshed_fragments apareciendo en cada página, normalmente comiéndose entre 800 y 1500 ms. Es WooCommerce preguntando “ha cambiado el icono del carrito en la cabecera?” en la home, en el blog, en categorías, en la página de contacto. Aunque el carrito esté vacío.
Caso real: una tienda de moda con checkout sobre Stripe e integración Bizum llegó al Black Friday con un pico de tráfico pagado en torno a 200 sesiones simultáneas. El checkout en sí aguantaba bien, pero los cart fragments serializaban a través de admin-ajax.php justo en el archivo de tienda, donde aterrizaba la mayor parte del tráfico de Meta Ads. Los workers de PHP-FPM en el plan WC Pro de Webempresa hicieron cola, el TTFB del archivo se fue a 8 segundos y la conversión cayó durante una hora y media hasta que el equipo limitó los fragments a páginas comerciales con un dequeue duro.
La regla de alcance más simple:
add_action( 'wp_enqueue_scripts', function() {
if ( is_cart() || is_checkout() || is_product() ) {
return;
}
wp_dequeue_script( 'wc-cart-fragments' );
}, 11 );
Plugins como Perfmatters o Asset CleanUp exponen esto como un toggle de UI. Cualquier camino vale; el objetivo es el mismo. Los cart fragments solo deben dispararse donde un icono de carrito necesite actualizarse en vivo.
Si necesitas un contador en cada página, guárdalo en localStorage y actualiza desde JavaScript en los eventos de añadir al carrito en lugar de consultar al servidor.
Bizum y la latencia de pasarela
Bizum integrado vía Redsys añade su propia latencia en el flujo de checkout. La negociación 3DS2 contra el ACS del banco emisor suele estar entre 800 y 1500 ms, fuera del control del servidor. Esto importa porque cualquier cart fragment ejecutándose en paralelo durante el checkout multiplica la percepción de lentitud justo en el momento más crítico. Limita los fragments a la página de carrito y excluye /finalizar-compra/ por completo del polling.
Object cache frente a transients en base de datos
WooCommerce usa transients de forma intensiva para rangos de precios de variaciones, búsquedas de términos de atributos y cálculos de envío. Sin un object cache, los transients viven en wp_options con autoload = 'yes', lo que significa que cada carga de página deserializa todos juntos en memoria.
Dos modos de fallo dignos de mención:
- Una librería online con 12k productos variables tenía transients
wc_var_prices_*acumulándose enwp_optionsdurante dos años. La fila autoload pasó de los 80 MB. Cada carga sin cache hacía un SELECT sobre unawp_optionscon 130k filas solo para arrancar WordPress. El TTFB se quedaba en 2,4 segundos antes de ejecutar lógica de negocio - Una tienda B2B sobre el plan WordPress Optimizado de Raiola corría con 30+ plugins y un TTFB base de 1,8 s. Tras identificar cuatro plugins (entre ellos un conector con Holded mal configurado) que escribían opciones autoloaded en cada guardado de producto y desactivarlos, el TTFB bajó a unos 600 ms sin tocar nada más
Elige un backend de cache y comprométete. O Redis o Memcached como object cache real, o quédate con transients en base de datos. Mezclar ambos genera bugs de invalidación difíciles de diagnosticar.
# Auditar tamaño autoload en wp_options
wp db query "SELECT SUM(LENGTH(option_value)) AS bytes, COUNT(*) AS rows FROM wp_options WHERE autoload = 'yes';"
# Localizar a los peores ofensores
wp db query "SELECT option_name, LENGTH(option_value) AS bytes FROM wp_options WHERE autoload = 'yes' ORDER BY bytes DESC LIMIT 20;"
Con Redis configurado a nivel de host, ya sea en el panel WC Pro de Webempresa, en OVH Hispano con Redis gestionado o en una VPS de Raiola con Redis instalado a mano, y object-cache.php colocado, los transients se mueven a RAM, baja la presión sobre autoload y el render de una página de producto en cache fría suele bajar del rango 500 a 800 ms a menos de 100 ms.
Excepciones de cache y qué nunca debe cachearse
El cache estático ayuda a archivos, fichas de producto y entradas de blog. Rompe activamente cualquier cosa que dependa de la sesión del usuario.
Exclusiones obligatorias para cualquier cache de página o Page Rule de Cloudflare:
/carrito/o/cart//finalizar-compra/o/checkout//mi-cuenta/o/my-account/- Cualquier URL con
?wc-ajax= - Cualquier URL con la cookie
woocommerce_items_in_cart - Endpoints REST bajo
/wp-json/wc/
Cloudflare APO puede cachear HTML para usuarios no logueados y respeta las cookies de sesión de WooCommerce si se configura bien, pero verifica con curl -I sobre carrito y checkout que recibes cf-cache-status: BYPASS. Una cache mal configurada que sirva el carrito de otra clienta no es una victoria de rendimiento, es una incidencia ante la AEPD.
Plugins de envío y rendimiento del checkout
El ecosistema de envío en España añade peso al checkout de formas que las guías genéricas pasan por alto.
- Plugins oficiales de SEUR, MRW y GLS suelen consultar la API del transportista en cada cambio de dirección durante el checkout. Activa el cache local de tarifas siempre que el plugin lo permita
- Plugins de impresión de etiquetas que se enganchan a la transición de estado del pedido pueden disparar peticiones síncronas desde
woocommerce_order_status_changed. Mueve esos hooks a Action Scheduler para que no bloqueen el cierre del pedido - Conectores con marketplaces tipo PrestaShop importado o Tiendanube en migración suelen dejar metadatos huérfanos en postmeta tras desinstalar; revísalo con
wp db queryantes de mover a HPOS
Consultas de productos variables y woocommerce_variable_children_args
Productos variables con 50+ variaciones cada uno son un multiplicador de JOIN. El archivo de una tienda de moda con 200 productos variables y 30 variaciones cada uno está haciendo JOIN contra wp_posts por 6000 productos hijos en cada carga.
Dos arreglos prácticos:
- Limita las variaciones cargadas en el desplegable a las que están en stock y son visibles
- Usa
woocommerce_variable_children_argspara excluir variaciones privadas y sin stock de la carga inicial en archivos
add_filter( 'woocommerce_variable_children_args', function( $args ) {
if ( is_shop() || is_product_category() ) {
$args['post_status'] = 'publish';
$args['meta_query'][] = array(
'key' => '_stock_status',
'value' => 'instock',
);
}
return $args;
} );
Inflado de tablas en Action Scheduler
Cada extensión de WooCommerce que hace trabajo en segundo plano usa Action Scheduler. Suscripciones, emails de seguimiento, recuperación de carrito abandonado, sincronización con ERP, integraciones con Mailrelay para newsletters transaccionales, todo acaba en wp_actionscheduler_actions y wp_actionscheduler_logs.
En un comercio con suscripciones activas y recuperación de carrito sobre Mailrelay o Mailchimp, estas tablas acumulan millones de acciones completadas y fallidas en menos de un año. Y se consultan en cada carga del admin para pintar el contador de la cola.
Script de mantenimiento:
# Disparar la purga de logs de Action Scheduler
wp action-scheduler run --hooks=action_scheduler/purge_logs
# Inspeccionar el tamaño de la cola
wp db query "SELECT status, COUNT(*) FROM wp_actionscheduler_actions GROUP BY status;"
# Confirmar que existen índices en status y scheduled_date_gmt
wp db query "SHOW INDEX FROM wp_actionscheduler_actions;"
WooCommerce programa una acción as_purge_logs por defecto, pero en tiendas que desactivan wp-cron y usan cron de sistema externo puede dejar de ejecutarse sin que nadie se entere.
Entrega de imágenes para galerías de producto
Las imágenes de producto suelen ser el payload más pesado. Tres reglas:
- AVIF para hero y galería, con WebP de fallback para versiones antiguas de Safari que sigan apareciendo en tu mix de tráfico
- Deja de permitir que clientes suban exportaciones raw de 4000 px. Limita el pipeline de subida a 2000 px en el lado largo
loading="lazy"nativo basta; elimina cualquier lazy loader JavaScript antiguo que entre en conflicto
Redimensiona antes de subir en lugar de confiar en que WordPress genere miniaturas al vuelo, lo que escribe en wp_postmeta e infla la base de datos.
Checklist de higiene de base de datos
Lanza esto mensualmente, con más frecuencia en tiendas con alto volumen de carrito abandonado:
# Eliminar sesiones expiradas
wp db query "DELETE FROM wp_woocommerce_sessions WHERE session_expiry < UNIX_TIMESTAMP();"
# Limpiar transients de WooCommerce
wp wc tool run clear_transients
# Optimizar tablas
wp db optimize
# Comprobar profundidad de la cola Action Scheduler
wp action-scheduler status
Programa por cron de sistema en lugar de wp-cron.php, que solo se dispara con tráfico.
Qué medir
Optimizar a ciegas lleva a optimizar lo que no toca. Elige tres señales y vigílalas semanalmente:
- TTFB en cache fría sobre el archivo de tienda y una ficha de producto
- Tiempo hasta el primer byte en
/carrito/y/finalizar-compra/con uno y cuatro artículos en el carrito - Recuento de filas y bytes totales en wp_options autoload
- Profundidad de cola Action Scheduler y acción pendiente más antigua
Query Monitor en staging te dice qué consultas dominan. New Relic o Datadog APM en producción muestran la distribución real. PageSpeed Insights es una prueba de humo, no un diagnóstico.
Última actualización
2026-04-01. Notas de campo de auditorías reales sobre WooCommerce en el último año, incluyendo tiendas alojadas en Webempresa, Raiola y OVH Hispano. Si tu tienda no encaja con estos patrones, los patrones no son el problema. Empieza por leer el slow query log.
Necesitas un desarrollador WooCommerce o optimización de velocidad WordPress? Hablemos.

