Se hai aperto una heatmap di Matomo e hai trovato il widget di Cal.com ridotto a una striscia di 200 pixel con una scrollbar interna, il tuo embed YouTube rimpicciolito a un placeholder, e i marker dei click ammassati nella parte alta del frame invece di essere distribuiti sui campi del form sottostanti, i dati dei click ci sono. Matomo ha registrato le coordinate correttamente. Quello che si è rotto è l'iframe nello screenshot sotto di loro. L'embed non si è mai espanso alla sua altezza reale quando il server Matomo ha ri-renderizzato la pagina, quindi qualsiasi dato su abbandono dei form, selezione del calendario o click sulle CTA dentro l'iframe finisce accatastato su se stesso in un piccolo rettangolo.
La soluzione più rapida è un'estensione Chrome gratuita che manteniamo noi, Matomo Heatmap Helper. Forza ogni iframe a un'altezza sensata durante la cattura (scrollHeight reale per gli embed same-origin, almeno 800px per quelli cross-origin) e ripristina lo stile originale dopo. Il resto di questo post spiega come fare la stessa cosa senza estensione, più le correzioni permanenti che sopravvivono a ogni cattura futura.
Come risolvere senza l'estensione
C'è uno snippet da console che tappa il problema subito prima di ogni cattura, e una manciata di fix permanenti da mettere in produzione insieme all'embed. Scegli quello che si adatta ai vincoli dell'iframe con cui hai a che fare.
Diagnostica cosa stai guardando
Prima di cambiare qualcosa, controlla se l'iframe è same-origin (puoi leggerne il contenuto) o cross-origin (non puoi). Apri la pagina in Chrome, premi F12 per aprire i DevTools, clicca sull'iframe nel pannello Elements così diventa $0, poi passa alla Console:
// Incolla nella console dopo aver selezionato l'iframe nel pannello 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'); }Se i due numeri non coincidono, è la differenza tra quello che ha visto il visitatore e quello che catturerà Matomo. Se la seconda riga dice cross-origin, la pagina padre non può leggere il contenuto dell'embed e la maggior parte degli snippet qui sotto lo salterà. In quel caso forza un'altezza minima.
Ridimensiona gli iframe same-origin dalla console
Se il tuo iframe è sulla stessa origine della pagina (un form self-hosted, un'app interna incorporata nel sito marketing, un prodotto collegato su un sottodominio che condivide la tua origine tramite document.domain), la pagina padre può leggerne l'altezza del contenuto direttamente. Incolla questo subito prima di triggerare la cattura della heatmap:
// Incolla nella console. Ridimensiona ogni iframe same-origin alla sua altezza reale.
document.querySelectorAll('iframe').forEach(f => {
try {
const doc = f.contentDocument;
if (doc) f.style.height = doc.body.scrollHeight + 'px';
} catch (e) { /* cross-origin, skip */ }
});Per gli iframe same-origin è tutto qui. Il browser blocca l'accesso a contentDocument sugli iframe cross-origin, quindi qualsiasi widget di terze parti viene silenziosamente saltato. Esegui di nuovo lo snippet diagnostico per confermare che le altezze sembrino giuste, poi triggera la cattura.
Forza un'altezza minima sugli iframe cross-origin
Per Cal.com, Calendly, YouTube, Stripe Checkout e qualsiasi altro embed di terze parti, la pagina padre non può leggere il contenuto dell'iframe. Puoi comunque impostare un'altezza sufficiente dall'esterno, che è il massimo che puoi fare senza la collaborazione del sito incorporato:
// Modifica MIN_HEIGHT per adattarla al tuo embed più alto (es. 800 per Cal.com, 1200 per form lunghi).
const MIN_HEIGHT = 800;
document.querySelectorAll('iframe').forEach(f => {
if (f.getBoundingClientRect().height < MIN_HEIGHT) {
f.style.height = MIN_HEIGHT + 'px';
}
});È una stima, non una misurazione. Impostala generosamente. Un iframe un po' troppo alto mostra spazio vuoto sotto il contenuto, il che è innocuo. Un iframe troppo corto continua a tagliare la parte bassa del form. Noi usiamo 800 per i widget di prenotazione e 1200 per i form di registrazione lunghi, e aggiustiamo pagina per pagina se un embed specifico ne ha bisogno di più.
Fix permanente quando controlli entrambi i lati: postMessage
Se l'iframe vive su un dominio che controlli (il tuo sottodominio, un prodotto collegato, uno strumento interno), la soluzione più pulita è un piccolo handshake postMessage. La pagina figlia riporta la sua altezza ogni volta che il contenuto si ricalcola, la pagina padre ascolta e applica. Niente più stime.
Nella pagina figlia incorporata:
// Aggiungi alla pagina figlia dell'iframe
const sendHeight = () => parent.postMessage(
{ type: 'iframe-height', height: document.body.scrollHeight },
'*'
);
new ResizeObserver(sendHeight).observe(document.body);Nella pagina padre:
// Aggiungi alla 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';
});
});Questo sopravvive a contenuto caricato in lazy, swap di font e step di form dinamici. Dà anche agli utenti reali un'esperienza più fluida, perché l'iframe smette di avere bisogno di una scrollbar propria.
Usa lo snippet embed ufficiale del vendor
Cal.com, Calendly e HubSpot distribuiscono tutti un protocollo postMessage di ridimensionamento integrato nei loro snippet embed ufficiali. Se hai incollato un <iframe src="..."> grezzo da una pagina di documentazione o da un thread di supporto, sostituiscilo con lo script drop-in del vendor. Lo hanno già risolto per te.
Per Cal.com si tratta del pacchetto @calcom/embed-snippet o del tag <script> dal loro embed builder. Per Calendly è lo snippet inline su calendly.com. Per HubSpot è il codice embed del form dalla scheda "Share" del form. Gli iframe grezzi che puntano all'URL renderizzato sono il pattern comune che si rompe qui, ed è quasi sempre un errore di copia-incolla più che una scelta deliberata.
iframe-resizer per embed cross-origin arbitrari che controlli
Se controlli entrambi i lati ma non vuoi scrivere il protocollo postMessage da solo, il pacchetto npm iframe-resizer gestisce i casi limite (contenuto lazy, font che caricano tardi, quirk di iOS). Due script, uno nella pagina padre, uno in quella figlia. Stesso effetto dello snippet sopra, con più test su tutti i browser.
Sostituisci l'iframe dove puoi
Per YouTube e Vimeo sulle pagine tracciate con heatmap, sostituisci l'iframe con un'immagine poster e inserisci l'iframe solo al click. Il visitatore ottiene comunque il video, e la heatmap cattura un target di click pulito e a dimensione piena invece di un piccolo placeholder. lite-youtube-embed è la versione canonica di questo pattern, ed è anche più veloce per gli utenti reali perché il pesante player di YouTube si carica solo quando qualcuno lo vuole davvero.
Non aiuta per i widget di prenotazione o gli iframe di pagamento, dove l'embed è l'interazione stessa. Aiuta per i video, le timeline social e tutto quello che è decorativo finché non ci si clicca.
Perché Matomo non riesce a dimensionare i tuoi iframe
Stessa causa radice dei problemi di font e immagini, con un dettaglio in più. La cattura degli screenshot di Matomo è un processo in due fasi. Prima, il tracker serializza il tuo DOM live in HTML e lo spedisce. Poi, il tuo server Matomo ri-renderizza quell'HTML per produrre lo screenshot che vedi nella vista heatmap. Nessuna sessione browser, nessun cookie, nessun Referer che punta al tuo dominio. Solo un file HTML che viene renderizzato a freddo da un IP diverso.
Per gli iframe, quel ri-render cattura quello che l'embed mostra nel suo stato predefinito. Un iframe è un documento separato. Il browser blocca la pagina padre dall'auto-dimensionare un iframe cross-origin, quindi l'altezza che vedi in produzione è quella che il codice embed del vendor ha negoziato tramite postMessage a runtime, più qualsiasi altezza CSS che il tuo team ha impostato per la UX live. Il renderer di Matomo non esegue JavaScript abbastanza a lungo perché quegli handshake vadano a buon fine. Gli iframe same-origin possono tecnicamente essere misurati, ma la maggior parte dei team si affida a un'altezza CSS impostata per la UX live piuttosto che per uno screenshot statico, quindi il renderer finisce comunque con un frame collassato al default.
Cosa sta davvero andando storto
Alcuni pattern che continuiamo a incontrare:
- Tag
<iframe src="https://cal.com/...">grezzi incollati in un CMS invece dello script embed ufficiale di Cal.com. Lo script ufficiale configura il postMessage. L'iframe grezzo no. - Embed inline di Calendly senza altezza impostata da nessuna parte nel CSS. Lo snippet di Calendly la imposta. Un iframe fatto a mano no.
- Form di HubSpot e Marketo che si caricano in modo asincrono dopo che la pagina è interattiva. Il form potrebbe essere alto 600px in produzione e 0px al momento della cattura, perché il JS del form non aveva ancora iniettato i campi quando Matomo ha serializzato il DOM.
- Iframe di YouTube e Vimeo al loro rapporto 16:9 predefinito per una colonna larga 560 pixel, renderizzato da Matomo a un viewport diverso, che finisce schiacciato.
- Iframe di Stripe Checkout e altri iframe di pagamento che intenzionalmente rimangono bloccati in un frame piccolo e rifiutano di espandersi per ragioni di sicurezza. Il vendor embedded decide l'altezza. Tu no.
- Iframe same-origin con
height: autoimpostato in CSS, che il browser calcola correttamente a runtime ma che il server di rendering statico tratta come 0 perché non esegue il layout pass abbastanza a lungo.
Se il tuo iframe è collassato nella heatmap, stai colpendo almeno uno di questi. Gli embed dei form di solito ne colpiscono due.
È la stessa famiglia di problemi che rompe font, immagini, container con scroll e header sticky nello stesso screenshot. Abbiamo scritto un post più lungo sugli screenshot rotti delle heatmap di Matomo che affronta il resto, più post separati su perché i font non si caricano e perché le immagini non si caricano visto che queste vengono su quasi altrettanto spesso.
Cosa faremmo noi
Se l'iframe vive sul tuo dominio, metti in produzione l'handshake postMessage. Risolve la heatmap, rende l'embed live più fluido e smetti di combattere con i layout shift per sempre.
Se è un embed di un vendor, usa lo snippet embed ufficiale del vendor. Cal.com, Calendly e HubSpot distribuiscono un protocollo di ridimensionamento funzionante nel loro. La maggior parte dei problemi che vediamo è gente che incolla l'URL dell'iframe renderizzato invece dello script embed.
Se nessuna delle due è percorribile (chat di terze parti, iframe di pagamento, widget di prenotazione di vendor su cui non puoi cambiare l'embed), l'estensione Chrome Matomo Heatmap Helper è quella che usiamo sui siti dei clienti dove non possiamo toccare lo stack. Forza un'altezza sensata durante la cattura (scrollHeight reale per same-origin, almeno 800px per cross-origin) e ripristina lo stile originale dopo, così la pagina live non viene toccata. Gratuita, open source, codice su GitHub.
Martez è il progetto più grande da cui è nata l'estensione. Collega Matomo con Meta Ads e Google Ads così ROAS, CLV e attribuzione stanno vicino alla tua web analytics invece di vivere in un foglio di calcolo separato. È in private beta. Unisciti alla waitlist se ti è utile.
La maggior parte delle volte, è questione di sostituire uno snippet del vendor o aggiungere un handshake postMessage. Vale la pena farlo una volta sola.