Por que los iframes aparecen colapsados en tu heatmap de Matomo (y como arreglarlo)

Los widgets de Cal.com, Calendly, YouTube y HubSpot se capturan como marcos diminutos con altura por defecto en los heatmaps de Matomo, muchas veces con barra de desplazamiento interna. Los clics se apilan sobre espacio vacio y se pierde toda la informacion de lo que hicieron los visitantes dentro del embed. Te cuento por que pasa y como lo resolvemos.

Si has abierto un heatmap de Matomo y has encontrado tu widget de Cal.com renderizado como una tira de 200 pixeles con barra de desplazamiento interna, tu embed de YouTube encogido hasta ser un placeholder y los marcadores de clics apilados en la parte superior del marco en vez de distribuidos por los campos del formulario de abajo, los datos de clics estan ahi tecnicamente. Matomo registro las coordenadas bien. Lo que esta roto es el iframe en la captura de pantalla que hay debajo. El embed nunca se expandio a su altura de contenido cuando el servidor de Matomo volvio a renderizar la pagina, asi que cualquier informacion sobre abandono de formularios, seleccion de calendario o clics en el CTA dentro del iframe acaba apilada sobre si misma en un rectangulo diminuto.

La solucion mas rapida es una extension gratuita de Chrome que mantenemos nosotros llamada Matomo Heatmap Helper. Fuerza a cada iframe a una altura razonable durante la captura (scrollHeight real para embeds del mismo origen, al menos 800px para los de origen cruzado) y restaura el estilo original despues. El resto de este post explica como hacer lo mismo sin una extension, mas las soluciones permanentes que sobreviven a cualquier captura futura.

Como arreglarlo sin la extension

Hay un fragmento de consola que tapa el problema justo antes de cada captura, y varias soluciones permanentes que puedes incluir con el embed. Elige la que encaje con las restricciones del iframe que estas tratando.

Diagnostica lo que tienes delante

Antes de cambiar nada, comprueba si el iframe es del mismo origen (puedes leer su contenido) o de origen cruzado (no puedes). Abre la pagina en Chrome, pulsa F12 para abrir DevTools, haz clic en el iframe en el panel Elements para que se convierta en $0 y cambia a la Consola:

js
// Pega esto en la consola despues de seleccionar el iframe en el panel Elements
console.log('Visible:', $0.getBoundingClientRect().height);
try { console.log('Content:', $0.contentDocument.body.scrollHeight); }
catch (e) { console.log('Content: cross-origin, can\'t read'); }

Si los dos numeros no coinciden, esa es la diferencia entre lo que vio el visitante y lo que va a capturar Matomo. Si la segunda linea dice cross-origin, la pagina padre no puede leer el contenido del embed y la mayoria de los fragmentos de abajo lo saltaran. Fuerza una altura minima en esos en su lugar.

Redimensiona los iframes del mismo origen desde la consola

Si tu iframe esta en el mismo origen que la pagina (un formulario auto-alojado, una app interna incrustada en tu sitio de marketing, un producto hermano en un subdominio que comparte tu origen via document.domain), la pagina padre puede leer su altura de contenido directamente. Pega esto justo antes de disparar la captura del heatmap:

js
// Pega esto en la consola. Redimensiona cada iframe del mismo origen a su altura de contenido.
document.querySelectorAll('iframe').forEach(f => {
  try {
    const doc = f.contentDocument;
    if (doc) f.style.height = doc.body.scrollHeight + 'px';
  } catch (e) { /* cross-origin, skip */ }
});

Eso es todo para el mismo origen. El navegador bloquea el acceso a contentDocument en iframes de origen cruzado, asi que cualquier widget de terceros se salta en silencio. Ejecuta el fragmento de diagnostico de nuevo para confirmar que las alturas tienen buena pinta y dispara la captura.

Fuerza una altura minima en iframes de origen cruzado

Para Cal.com, Calendly, YouTube, Stripe Checkout y cualquier otro widget de terceros, la pagina padre no puede leer el contenido del iframe. Puedes establecer una altura suficientemente grande desde fuera, que es lo mejor que puedes hacer sin la cooperacion del sitio incrustado:

js
// Edita MIN_HEIGHT para que encaje con tu embed mas alto (p. ej. 800 para Cal.com, 1200 para formularios largos).
const MIN_HEIGHT = 800;
document.querySelectorAll('iframe').forEach(f => {
  if (f.getBoundingClientRect().height < MIN_HEIGHT) {
    f.style.height = MIN_HEIGHT + 'px';
  }
});

Esto es una estimacion, no una medicion. Ponla generosa. Un iframe que sea algo demasiado alto muestra espacio en blanco debajo del contenido, lo cual es inocuo. Un iframe demasiado corto sigue cortando la parte inferior del formulario. Nosotros usamos 800 por defecto para widgets de reservas y 1.200 para formularios de registro largos, y ajustamos por pagina si algun embed necesita mas.

Solucion permanente cuando controlas los dos lados: postMessage

Si el iframe vive en un dominio que controlas (tu propio subdominio, un producto hermano, una herramienta interna), la opcion mas limpia es un pequeno protocolo de intercambio con postMessage. La pagina hija informa de su altura cada vez que el contenido cambia de tamano, la pagina padre escucha y aplica. Sin mas estimaciones.

En la pagina hija incrustada:

js
// Anade esto a la pagina hija del iframe
const sendHeight = () => parent.postMessage(
  { type: 'iframe-height', height: document.body.scrollHeight },
  '*'
);
new ResizeObserver(sendHeight).observe(document.body);

En la pagina padre:

js
// Anade esto a la pagina padre
window.addEventListener('message', e => {
  if (e.data?.type !== 'iframe-height') return;
  document.querySelectorAll('iframe').forEach(f => {
    if (f.contentWindow === e.source) f.style.height = e.data.height + 'px';
  });
});

Esto sobrevive a contenido cargado de forma diferida, cambios de fuentes y pasos de formularios dinamicos. Ademas da a tus usuarios reales una experiencia mas fluida, porque el iframe deja de necesitar su propia barra de desplazamiento.

Usa el fragmento de embed oficial del proveedor

Cal.com, Calendly y HubSpot incluyen un protocolo de redimensionado via postMessage en sus fragmentos de embed oficiales. Si copiaste y pegaste un <iframe src="..."> simple de una pagina de documentacion o un hilo de soporte, sustituye eso por el script que proporciona el propio proveedor. Ellos ya lo han resuelto por ti.

Para Cal.com es el paquete @calcom/embed-snippet o la etiqueta <script> de su embed builder. Para Calendly es el fragmento de embed inline de calendly.com. Para HubSpot es el codigo de embed del formulario de la pestana "Share". Los iframes simples apuntando a la URL renderizada son el patron habitual que falla aqui, y casi siempre es un error de copiar y pegar mas que una decision deliberada.

iframe-resizer para embeds de origen cruzado arbitrarios que controlas

Si controlas los dos lados pero no quieres escribir el protocolo postMessage tu mismo, el paquete npm iframe-resizer gestiona los casos extremos (contenido lazy, fuentes que cargan tarde, particularidades de iOS). Dos scripts, uno en el padre, uno en el hijo. El mismo efecto que el fragmento de arriba, con mas pruebas en distintos navegadores.

Reemplaza el iframe donde puedas

Para YouTube y Vimeo en paginas con seguimiento de heatmap, cambia el iframe por una imagen de poster e inserta el iframe solo al hacer clic. El visitante sigue teniendo acceso al video, y el heatmap captura un objetivo de clic limpio y a tamano completo en vez de un placeholder diminuto. lite-youtube-embed es la version canonica de este patron, y ademas es mas rapido para los usuarios reales porque el reproductor de YouTube solo carga cuando alguien realmente lo quiere.

Esto no sirve para widgets de reservas o iframes de pago, donde el embed es la propia interaccion. Si ayuda para video, cronologias de redes sociales y cualquier otra cosa donde el iframe es decorativo hasta que se hace clic.

Por que Matomo no puede dimensionar tus iframes

La misma causa raiz que la version del problema con fuentes e imagenes, con un agravante extra. La captura de pantalla de Matomo es un proceso de dos pasos. Primero, el tracker serializa tu DOM en vivo en HTML y lo envia. Despues, tu servidor de Matomo vuelve a renderizar ese HTML para producir la captura que ves en la vista del heatmap. Sin sesion de navegador, sin cookies, sin Referer apuntando a tu dominio. Solo un archivo HTML siendo renderizado en frio desde una IP diferente.

Para los iframes, ese re-renderizado captura lo que el embed muestra en su estado por defecto. Un iframe es un documento separado. El navegador bloquea que la pagina padre dimensione automaticamente un iframe de origen cruzado, asi que la altura que ves en produccion es la que el codigo de embed del proveedor nego via postMessage en tiempo de ejecucion, mas cualquier altura CSS que tu equipo definio para la UX en vivo. El renderizador de Matomo no ejecuta JavaScript el tiempo suficiente para que esos intercambios lleguen a completarse. Los iframes del mismo origen tecnicamente pueden medirse, pero la mayoria de equipos confian en una altura CSS pensada para la UX en vivo en vez de para una captura estatica, asi que el renderizador acaba con un marco colapsado por defecto de todas formas.

Que es lo que falla en realidad

Unos cuantos patrones con los que nos seguimos topando:

  • Etiquetas <iframe src="https://cal.com/..."> simples pegadas en un CMS en vez del script de embed oficial de Cal.com. El script oficial conecta postMessage. El iframe simple no.
  • Embeds inline de Calendly donde la altura no esta definida en ninguna parte del CSS. El propio fragmento de Calendly la define. Un iframe hecho a mano no.
  • Formularios de HubSpot y Marketo que cargan de forma asincrona despues de que la pagina sea interactiva. El formulario puede tener 600px de alto en produccion y 0px en el momento de la captura, porque el JavaScript del formulario no habia inyectado los campos todavia cuando Matomo serializo el DOM.
  • Iframes de YouTube y Vimeo en su proporcion 16:9 por defecto para una columna de 560 pixeles de ancho, renderizados por Matomo con un viewport diferente y acabando aplastados.
  • Stripe Checkout y otros iframes de pago que bloquean intencionalmente un marco pequeno por defecto y se niegan a expandirse por razones de seguridad. El proveedor incrustado decide la altura. Tu no.
  • Iframes del mismo origen con height: auto en CSS, que el navegador calcula correctamente en tiempo de ejecucion pero que el servidor de renderizado estatico trata como 0 porque no ejecuta el layout pass el tiempo suficiente.

Si tu iframe esta colapsado en el heatmap, estas cayendo en al menos uno de estos. Los embeds de formularios suelen caer en dos.

Este es el mismo tipo de problema que rompe fuentes, imagenes, contenedores con scroll y encabezados sticky en la misma captura. Hemos escrito un post mas extenso sobre capturas de heatmap de Matomo rotas que recorre el resto, mas posts separados sobre por que las fuentes no cargan y por que las imagenes no cargan ya que aparecen casi con la misma frecuencia.

Lo que hariamos nosotros

Si el iframe vive en tu propio dominio, implementa el protocolo postMessage. Arregla el heatmap, mejora el embed en vivo y dejas de pelear con los layout shifts para siempre.

Si es un embed de terceros, usa el fragmento de embed oficial del proveedor. Cal.com, Calendly y HubSpot incluyen un protocolo de redimensionado que funciona en el suyo. La mayoria de los fallos que vemos son gente pegando la URL del iframe renderizado en vez del script de embed.

Si ninguna opcion es alcanzable (chat de terceros, iframes de pago, widgets de reservas de proveedores que no puedes cambiar), la extension de Chrome Matomo Heatmap Helper es lo que usamos en los sitios de clientes donde no podemos cambiar el stack. Fuerza una altura razonable durante la captura (scrollHeight real para el mismo origen, al menos 800px para origen cruzado) y restaura el estilo original despues, de modo que la pagina en vivo no se ve afectada. Gratuita, de codigo abierto, codigo en GitHub.

Martez es el proyecto mas amplio del que salio la extension. Conecta Matomo con Meta Ads y Google Ads para que el ROAS, el CLV y la atribucion esten junto a tu analitica web en vez de en una hoja de calculo separada. Esta en beta privada. Apuntate a la lista de espera si eso te resulta relevante.

La mayoria de las veces, esto se resuelve con un cambio de fragmento del proveedor o un protocolo postMessage. Vale la pena configurarlo una vez.

Por que los iframes aparecen colapsados en tu heatmap de Matomo (y como arreglarlo) - Martez Blog