Cómo optimizar WordPress y mejorar el LCP

Una web puede parecer rápida cuando la abrimos desde nuestro ordenador con una buena conexión, pero la experiencia cambia bastante cuando la visitamos desde un móvil y usando datos. Al analizar el rendimiento de mi web usando Wakaris en un dispositivo de gama media con conexión 4G apareció un problema bastante claro: el contenido principal tardaba 7,7 segundos en mostrarse. Es un tiempo suficientemente alto como para que muchos usuarios abandonen la página antes incluso de empezar a leer. En este artículo veremos qué métricas estaban afectando al rendimiento, cómo interpretarlas y, sobre todo, qué cambios podemos aplicar para intentar reducir el LCP desde esos 7,7 segundos hasta los 2,5 segundos o menos recomendados. Vamos a ver como optimizar WordPress y mejorar el LCP de nuestra web.

Tabla de contenidos

0. Las métricas y valores iniciales

Vamos a explicar que es cada métrica

LCP – Largest Contentful Paint: mide cuánto tarda en mostrarse el elemento de contenido más grande visible al cargar la página, normalmente una imagen, un banner o un bloque grande de texto. Es una forma de medir cuándo el usuario siente que “la página ya ha cargado”. Como referencia, se considera bueno estar en ≤ 2,5 s.

TBT – Total Blocking Time: mide cuánto tiempo queda bloqueado el hilo principal del navegador por tareas largas, normalmente causadas por JavaScript. Mientras está bloqueado, la web puede verse cargada pero responder tarde a clics, scrolls o interacciones. Cuanto más bajo, mejor; ≤ 200 ms suele considerarse un buen objetivo en herramientas como Lighthouse.

CLS – Cumulative Layout Shift: mide cuánto se mueven inesperadamente los elementos de la página mientras carga. Por ejemplo, estás a punto de pulsar un botón y aparece una imagen arriba que desplaza todo hacia abajo. Lo ideal es un valor de ≤ 0,1.

Encontré por Linkedin hace poco una post aleatorio de un usuario que hablaba de una web de medición de rendimiento, llamada Wakaris y me gustó bastante. Y la he usado para ir iterando. Es algo similar a GTMetrix o Google PageSpeed Insights Y los resultados eran muy malos. Y en varios campos, no solo en el rendimiento. Inicialmente tuve estos resultados.

LCP 7,7

TBT 64 ms

CLS 0,01

rendimiento web via wakaris

Rendimiento, era uno de los peores valores de todo el análisis.

Antes de entrar al fondo, unas consideraciones generales para mejorar el rendimiento.

  • Optimizar las imágenes: reducir dimensiones, comprimirlas y utilizar formatos modernos como WebP o AVIF.
  • Evitar plugins innecesarios: cada plugin puede añadir CSS, JavaScript, consultas a base de datos o peticiones externas.
  • Configurar caché: evitar que WordPress tenga que generar la misma página desde cero en cada visita.
  • Usar una CDN: especialmente útil para servir imágenes, CSS, JavaScript y otros recursos estáticos.
  • Reducir CSS y JavaScript: minimizar archivos, eliminar código no utilizado y evitar cargar scripts donde no hacen falta.
  • Retrasar recursos no críticos: usar defer, async o carga diferida cuando tenga sentido.
  • Aplicar lazy loading: cargar imágenes y otros elementos solo cuando el usuario se acerca a ellos.
  • Limitar scripts de terceros: analytics, publicidad, fuentes externas, widgets o embeds pueden penalizar mucho el rendimiento.
  • Mantener WordPress, PHP y plugins actualizados: además de seguridad, las nuevas versiones suelen incluir mejoras de rendimiento.
  • Revisar el servidor: CPU, memoria, almacenamiento, versión de PHP y configuración de la base de datos también influyen.
  • Reducir redirecciones y peticiones HTTP: cada salto y cada recurso adicional añade latencia.

1. Optimizar la imagen de fondo

Analizando una grabación de Safari apareció rápidamente uno de los recursos más pesados de la página:

fondo.jpg
394675 bytes

Era la imagen que utilizo como fondo general del blog.

La imagen estaba en JPG, con resolución 1920×1080 y pesaba aproximadamente 395 KB.

La primera optimización fue muy sencilla: convertirla a WebP, manteniendo una calidad visual suficiente.

El resultado:

ANTES
fondo.jpg
~395 KB

DESPUÉS
fondo.webp
~187 KB

Es prácticamente la mitad de datos para mostrar exactamente el mismo fondo.

Después del cambio, Safari confirmó que la nueva imagen estaba siendo utilizada:

Content-Type: image/webp
Content-Length: 187212

No era suficiente para explicar por sí sola un LCP de 7,7 segundos, pero sí era una optimización evidente que merecía la pena hacer.


2. Descubriendo que CloudFront no comprimía la página principal

El siguiente paso fue revisar cuánto pesaba realmente el HTML.

Con curl:

curl -sS \
  -H 'Accept-Encoding: br, gzip' \
  -D /tmp/before-headers.txt \
  -o /dev/null \
  -w 'download=%{size_download} bytes\nttfb=%{time_starttransfer}s\ntotal=%{time_total}s\n' \
  https://www.rubenortiz.es/

El resultado era:

download=108015 bytes
ttfb=0.399917s
total=0.518430s

Es decir, la home transfería alrededor de 108 KB de HTML.

Revisando los headers había otro detalle importante: no aparecía ningún:

Content-Encoding: gzip

ni:

Content-Encoding: br

CloudFront estaba entregando el HTML sin compresión.

Configuración de CloudFront

Revisando Terraform encontré que los behaviors específicos ya tenían compresión:

compress = true

Por ejemplo:

ordered_cache_behavior {
  path_pattern = "/wp-content/uploads/*"

  ...

  compress = true
}

Sin embargo, faltaba precisamente en el behavior principal:

default_cache_behavior {
    ...
}

La solución fue añadir:

compress = true

Quedando:

default_cache_behavior {
  ...

  compress               = true
  viewer_protocol_policy = "redirect-to-https"

  min_ttl     = 0
  default_ttl = 3600
  max_ttl     = 86400
}

Después del terraform apply comprobé la compresión usando un CSS generado por Autoptimize.

Antes tenía un tamaño de aproximadamente:

271802 bytes

La prueba:

curl -sS \
  -H 'Accept-Encoding: gzip' \
  -D /tmp/css-compress-test.txt \
  -o /dev/null \
  -w 'download=%{size_download} bytes\n' \
  'https://www.rubenortiz.es/wp-content/cache/autoptimize/css/autoptimize_xxx.css?cf-compress-test=1'

devolvió:

content-type: text/css
content-encoding: gzip
x-cache: Miss from cloudfront

download=45685 bytes

Por tanto:

CSS
~272 KB → ~46 KB

Una reducción de aproximadamente el 83 %.

CloudFront estaba comprimiendo correctamente.

Pero el HTML seguía sin hacerlo.


3. El problema estaba también en Nginx

Probé entonces directamente contra Elastic Beanstalk, evitando completamente CloudFront:

curl -ks \
  -H 'Accept-Encoding: gzip' \
  -D - \
  -o /dev/null \
  'https://mi-entorno.region.elasticbeanstalk.com/'

La respuesta era:

Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked

Pero seguía sin aparecer:

Content-Encoding: gzip

Por tanto, Nginx tampoco estaba comprimiendo las respuestas dinámicas de WordPress.

Añadí configuración gzip:

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;

gzip_types
    text/plain
    text/css
    application/json
    application/javascript
    application/x-javascript
    text/xml
    application/xml
    application/xml+rss
    text/javascript
    image/svg+xml;

Pero inicialmente seguía sin funcionar.

Aquí apareció uno de los detalles más interesantes del debugging.


4. Gzip funcionaba… pero solo en HTTP

Al inspeccionar la configuración efectiva:

sudo nginx -T 2>&1 | grep -nE 'listen .*443|listen .*80|server_name'

obtuve:

114:  listen 80 default_server;
1168: listen 443 ssl;
1170: server_name www.rubenortiz.es;

Y al revisar dónde se encontraba gzip on:

sudo nginx -T 2>&1 | sed -n '100,150p'

aparecía:

server {
    listen 80 default_server;

    ...

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_min_length 1024;

    ...
}

Ahí estaba el error.

La configuración gzip estaba dentro del server que escuchaba en puerto 80.

Pero tenía otro bloque independiente para HTTPS:

server {
    listen 443 ssl;
    server_name www.rubenortiz.es;

    ...
}

Por tanto, el servidor de puerto 443 no heredaba esa configuración.

La solución fue mover gzip a la configuración global de Nginx mediante:

.platform/nginx/conf.d/gzip.conf

De esa manera las directivas quedan dentro de http {} y las heredan tanto el servidor HTTP como el HTTPS:

http
 ├── gzip on
 │
 ├── server :80
 │
 └── server :443

Después validé y recargué Nginx:

sudo nginx -t && sudo systemctl reload nginx

5. Verificando gzip directamente contra Elastic Beanstalk

Primero probé nuevamente el CSS:

curl -ks --http1.1 \
  -H 'Accept-Encoding: gzip' \
  -D - \
  -o /dev/null \
  'https://mi-entorno.region.elasticbeanstalk.com/file.css'

Ahora sí:

HTTP/1.1 200 OK
Content-Type: text/css
Vary: Accept-Encoding
Content-Encoding: gzip

Después llegó la prueba realmente importante: el HTML de WordPress.

curl -ks --http1.1 \<br>  -H 'Accept-Encoding: gzip' \<br>  -D - \<br>  -o /dev/null \<br>  -w 'download=%{size_download} bytes\nttfb=%{time_starttransfer}s\ntotal=%{time_total}s\n' \<br>  'https://mi-entorno.region.elasticbeanstalk.com/'

Resultado:

Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Vary: Accept-Encoding
Content-Encoding: gzip

download=21947 bytes
ttfb=0.803649s
total=0.879924s

El HTML había pasado de aproximadamente:

108 KB

a:

22 KB

Una reducción cercana al 80 %.


6. Verificando el recorrido completo con CloudFront

Faltaba comprobar que funcionase también desde Internet pasando por CloudFront:

curl -sS \
  -H 'Accept-Encoding: gzip' \
  -D - \
  -o /dev/null \
  -w 'download=%{size_download} bytes\nttfb=%{time_starttransfer}s\ntotal=%{time_total}s\n' \
  'https://www.rubenortiz.es/?gzip-test=4'

Primera petición:

content-encoding: gzip
vary: Accept-Encoding
x-cache: Miss from cloudfront

download=21769 bytes

Segunda petición:

content-encoding: gzip
vary: Accept-Encoding
x-cache: Hit from cloudfront
age: 8

download=21769 bytes
ttfb=0.797534s
total=0.798718s

La cadena completa ya estaba funcionando:

Browser
   │
   │ Accept-Encoding: gzip
   ▼
CloudFront
   │
   ▼
Nginx
   │
   │ gzip
   ▼
WordPress

D

7. Diferir JQuery

Uno de los recursos que Lighthouse marcaba como bloqueante era jquery.min.js, con unos 31 KB y cerca de 920 ms dentro de la ruta crítica de renderizado. El problema no era tanto el peso del archivo como el hecho de que el navegador debía descargarlo y procesarlo antes de continuar con el render inicial de la página, pudiendo retrasar métricas como FCP y LCP.

En Autoptimize ya tenía activada la opción “Do not aggregate but defer”, que permite cargar los scripts individuales con el atributo defer, pero jQuery estaba excluido explícitamente mediante js/jquery/jquery.min.js. Tras eliminar únicamente esa exclusión y vaciar la caché de Autoptimize, tanto jquery-core como jquery-migratecomenzaron a servirse con defer, manteniendo intacta la funcionalidad del sitio. La siguiente ejecución de Lighthouse confirmó que jQuery había desaparecido completamente de la sección Render-blocking requests, quedando únicamente el CSS generado por Autoptimize.

El ahorro global estimado de recursos bloqueantes pasó de aproximadamente 1.110 ms a 980 ms.

Las métricas también mejoraron ligeramente

FCP de 1,7 s a 1,5 s,

LCP de 3,6 s a 3,4 s y

Speed Index de 3,5 s a 3,3 s

aunque una variación tan pequeña puede entrar dentro de la dispersión normal entre ejecuciones de Lighthouse. Lo importante en este caso es que se eliminó correctamente un recurso JavaScript de la ruta crítica sin romper dependencias ni introducir errores en la web.


8. Ampliar la caché a un año

Además de reducir el peso de los recursos, revisé la política de caché de los archivos generados por Autoptimize.

Wakari detectaba que el CSS principal, por ejemplo autoptimize_b7e1ec6b8133370482e43a1431d3a757.css, tenía una caché de solo 30 días (max-age=2592000). Como estos archivos incluyen un hash en el nombre y cambian de URL cuando cambia su contenido, pueden cachearse durante mucho más tiempo sin riesgo.

Añadí una regla específica en Nginx para /wp-content/cache/autoptimize/ con expires 1y, dejando intacta la política general de 30 días para el resto de estáticos.

Tras recargar Nginx, el mismo recurso pasó a devolver Cache-Control: max-age=31536000 y una fecha de expiración a un año vista, eliminando así la advertencia de caché corta y mejorando las visitas recurrentes al evitar descargas innecesarias.


9. Cambiando font-display: block por swap

Uno de los puntos detectados por Lighthouse estaba relacionado con la fuente de iconos de Flatsome (fl-icons), que se declaraba con font-display: block. Este comportamiento puede retrasar la visualización del contenido que depende de esa fuente mientras el navegador espera a que termine de cargarse.

Para corregirlo, añadí una pequeña personalización en el tema hijo de WordPress que modifica dinámicamente esa declaración y la cambia a font-display: swap, permitiendo que el navegador muestre el contenido inmediatamente y sustituya la fuente cuando esté disponible, sin modificar directamente el tema padre.

Tras desplegar el cambio en producción, el LCP mejoró de 3,4 s a 2,5 s, una reducción de aproximadamente 0,9 segundos (~26%), mientras que el FCP se mantuvo en 1,5 s, el TBT en 0 ms y el CLS en 0,007. El Speed Index pasó de 3,3 s a 3,5 s, una variación pequeña que puede entrar dentro de la dispersión normal entre ejecuciones de Lighthouse.

Aunque una única medición no permite atribuir toda la mejora exclusivamente a este cambio, el resultado fue especialmente positivo porque se redujo de forma notable el LCP sin introducir bloqueos de JavaScript ni inestabilidad visual.

10. Resultado

Las principales mejoras obtenidas fueron:

RecursoAntesDespués
Imagen de fondo~395 KB JPG187 KB WebP
HTML home~108 KB~22 KB gzip
CSS Autoptimize~272 KB~46 KB gzip

Especialmente significativa es la reducción del HTML:

108015 bytes
     ↓
21788 bytes

Aproximadamente un 80 % menos de datos transferidos.

En el caso del CSS:

271802 bytes
     ↓
45685 bytes

la reducción es de aproximadamente un 83 %.

Una nota sobre el TTFB

Durante las pruebas apareció una petición con un TTFB de más de 6 segundos. Antes de concluir que había un problema grave en el origin, repetí varias peticiones forzando distintos cache misses:

MISS 1  TTFB=1.648s
MISS 2  TTFB=0.831s
MISS 3  TTFB=0.858s
MISS 4  TTFB=0.674s
MISS 5  TTFB=0.739s

El valor de 6 segundos era por tanto un outlier, no el comportamiento habitual del servidor.

Después de invalidar CloudFront, una petición normal a la home devolvió:

content-encoding: gzip
x-cache: Miss from cloudfront

download=21788 bytes
ttfb=0.908004s
total=0.984070s

Incluso en un cache miss, la respuesta completa quedaba por debajo de un segundo en esa prueba.

Conclusión y valores finales

El objetivo inicial era mejorar un LCP de 7,7 segundos, pero antes de buscar optimizaciones complejas había bastante margen en la propia transferencia de recursos.

Tras el cambio, una nueva ejecución de Lighthouse redujo el LCP de 3,4 s a 2,5 s. Al tratarse de una métrica sensible a las condiciones de cada ejecución, será necesario observar varias mediciones y los datos reales de usuarios para confirmar que la mejora se mantiene.

Dos cambios relativamente sencillos han reducido considerablemente el volumen inicial:

Imagen pesada    → WebP
HTML/CSS/JS      → gzip

Y durante el proceso apareció además un detalle fácil de pasar por alto: tener gzip on en Nginx no significa necesariamente que todas las conexiones lo estén utilizando. En mi caso estaba dentro del server de puerto 80 mientras el tráfico real llegaba por el server HTTPS de puerto 443.

Mover la configuración al contexto global solucionó el problema.

El siguiente paso será volver a medir el LCP con caché fría. Ahora que imágenes, HTML y recursos estáticos tienen un peso mucho menor, toca localizar cuál es el siguiente elemento que limita el renderizado inicial.

En la última revisión de Wakaris se aprecia mucho la mejora en rendimiento:

valores de wakaris en rubenortiz.es
EspecialidadAntesDespuésCambio
Accesibilidad78%94%+16 pp
Experiencia100%100%=
Inteligencia artificial50%100%+50 pp
Legal33%86%+53 pp
Marketing80%67%-13 pp
Posicionamiento78%89%+11 pp
Presencia33%100%+67 pp
Rendimiento33%83%+50 pp
Seguridad63%82%+19 pp

Es decir según Wakaris, pasé de un 33% de rendimiento a un 83%.

En PageSpeed obtengo un 99% en versión web

Y la parte móvil también está bastante optimizada:

rendimiento de rubenortiz.es en pagespeed

Links

¿Te ha ayudado este artículo?

☕ Invítame a un café

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Rubenortiz.es
Información sobre privacidad

Este sitio web utiliza cookies para ofrecerte la mejor experiencia de usuario posible. La información sobre cookies se almacena en tu navegador y realiza funciones como reconocerte cuando vuelve a nuestro sitio web y ayudarme a comprender qué secciones de la web resultan más interesantes y útiles.