Saltar al contenido
crawlforgeEnglish
Se rompió6 de agosto de 20269 min de lecturaRead in English

Tu herramienta de auditoría no está viendo tus imágenes

Un medio con 18.000 páginas de fotos nos daba cero imágenes pesadas. La tabla estaba vacía. El motivo es un plugin de carga diferida que usa medio WordPress del mundo.

Estábamos revisando el informe de un rastreo de 20.000 URLs de un medio de comunicación y algo no cuadraba. ASSET-IMG-HEAVY, la regla que avisa de imágenes por encima de 200 KB, daba cero hallazgos.

Un periódico digital. Quince años publicando artículos con foto. Cero imágenes pesadas.

Miramos la base de datos directamente:

SELECT COUNT(*) FROM images;
-- 0

Ni una fila. En el mismo rastreo, 20.000 URLs y 18.000 páginas HTML, y la tabla de imágenes vacía.

Qué estaba pasando

El sitio usa LiteSpeed Cache, uno de los plugins de optimización más extendidos de WordPress. Entre otras cosas, hace carga diferida de imágenes, y para eso reescribe el HTML así:

<img data-lazyloaded="1"
     src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAwIDgwMCI+PC9zdmc+"
     data-src="https://ejemplo.com/wp-content/uploads/2026/07/foto.jpg"
     alt="Pie de foto del artículo">

El atributo src, que es donde todo el mundo mira, contiene un SVG transparente codificado en base64. La URL de verdad está en data-src, y el navegador la mueve al src cuando la imagen se acerca al viewport.

Nuestro parser leía el src, encontraba una URI data:, la descartaba por no ser una URL rastreable, y seguía adelante. Correctamente, además: una URI data: no es una imagen que se pueda pedir al servidor. El problema es que tampoco miraba si había una URL real al lado.

Lo que se pierde

Todo lo que dependa de conocer la imagen:

  • Peso. No sabes que estás sirviendo un JPEG de 1,9 MB en la portada.
  • Estado. Una imagen que devuelve 404 no aparece por ningún lado.
  • Formato. No puedes decir cuántas siguen en JPEG pudiendo estar en WebP.
  • Recuento. El informe dice que el sitio tiene cero imágenes.

Y lo que más nos molestó: el alt sí lo leíamos, porque ese atributo el plugin lo deja donde estaba. Así que reportábamos correctamente imágenes sin texto alternativo, en un sitio del que creíamos que no tenía imágenes. El informe se contradecía a sí mismo y nadie lo había notado.

El arreglo

Cuando el src es una URI data:, buscar la URL real en los atributos que usan los plugins de carga diferida. Por orden de uso:

  1. data-src — LiteSpeed Cache, lazysizes, a3 Lazy Load, Smush, EWWW
  2. data-lazy-src — WP Rocket, Jetpack Lazy Images
  3. data-original — el jQuery lazyload clásico, todavía vivo en sitios antiguos
  4. El primer candidato de data-srcset

No nos fiamos de la lista y fuimos a comprobarlo: descargamos la portada y un artículo del sitio real. LiteSpeed emitía data-lazyloaded="1" con data-src y data-srcset en 45 de las 51 imágenes de la portada.

También decidimos no leer el <noscript>. Muchos plugins dejan ahí una copia del <img> original como alternativa para navegadores sin JavaScript, y parecía la fuente más fiable. Dos razones para descartarlo: en las páginas que medimos no había ninguno, y en los sitios que sí lo emiten leerlo duplicaría cada imagen, obligando a deduplicar después.

El resultado

El mismo sitio, un rastreo de 300 URLs con el arreglo puesto:

SELECT COUNT(*) FROM images;
-- 5945

Y en un rastreo completo, las reglas que llevaban meses calladas:

high    ASSET-IMG-BROKEN     2,193
medium  ASSET-IMG-HEAVY     43,980

Cuarenta y tres mil imágenes por encima del umbral, y dos mil que devuelven error. En un medio de comunicación eso es probablemente el hallazgo con más impacto de toda la auditoría, y llevaba desde el principio siendo invisible.

Un ejemplo de las que aparecieron:

https://ejemplo.com/wp-content/uploads/2026/07/segunda-rfef-grupo-v.png
  1.927.124 bytes  ·  límite 204.800  ·  usada en 1 página

Casi dos megas en un PNG que podría ser un WebP de 150 KB.

Qué hacer si tus imágenes pesan

Encontrarlas es la mitad. La otra mitad es que el informe te diga por dónde empezar, y ahí el recuento de páginas que usan cada imagen importa más que el peso:

crawlforge report crawl.sqlite --rule ASSET-IMG-HEAVY

Una imagen de 1,9 MB usada en una sola entrada de hace tres años no es urgente. La misma imagen en la cabecera de la plantilla, servida en cada página, es lo primero que hay que tocar. Nuestro detalle incluye used_by_pages justo por eso.

El orden que seguimos nosotros: primero las de plantilla, después las de portada y categorías, y por último el archivo, que casi nunca compensa salvo que tengas un proceso automático.

Cómo saber si te está pasando

Da igual la herramienta que uses. Rastrea un sitio tuyo con carga diferida y mira el recuento de imágenes. Si sale sospechosamente bajo, o cero, tienes el mismo problema.

La comprobación rápida en tu propio HTML:

curl -s https://tusitio.com/ | grep -c 'data-src='

Si eso devuelve un número alto y tu auditoría dice que el sitio tiene pocas imágenes, ya sabes por dónde va.

Lo que nos llevamos

Este fallo no lo habría encontrado ningún test unitario, y de hecho no lo encontró: teníamos 810 pasando en verde. Los fixtures de imágenes que habíamos escrito usaban <img src="/foto.jpg">, como los ejemplos de los libros.

El HTML real no se parece a los ejemplos de los libros. Lleva plugins encima.

Hay una segunda parte que descubrimos después, y es cara: al empezar a leer las imágenes, la tabla pasó de cero a 4,4 millones de filas, y eso destapó un índice que faltaba en el motor. La pasada final de un rastreo grande se fue a más de ocho horas. Pero eso da para otro artículo.

Se construye en público

Cada dos semanas: mediciones, fallos y ejemplos de uso. Nada más.

← Volver a la bitácora