Pourquoi les iframes s'affichent reduits dans ta heatmap Matomo (et comment y remedier)

Les embeds Cal.com, Calendly, YouTube et HubSpot apparaissent souvent comme de minuscules frames a hauteur par defaut dans les heatmaps Matomo, avec une barre de defilement interne. Les clics s'entassent dans le vide et toute information sur ce que les visiteurs ont fait dans l'embed est perdue. Voici pourquoi ca arrive et comment on y remédie.

Si tu as ouvert une heatmap Matomo et trouvé ton widget Cal.com réduit à un bandeau de 200 pixels avec une barre de défilement interne, ton embed YouTube ratatiné en placeholder, et les marqueurs de clics entassés en haut du frame au lieu d'être répartis sur les champs du formulaire en dessous, les données de clics sont techniquement là. Matomo a bien enregistré les coordonnées. Ce qui est cassé, c'est l'iframe dans la capture d'écran en dessous. L'embed ne s'est jamais étendu à sa hauteur de contenu quand le serveur Matomo a re-rendu la page, donc toute information sur l'abandon de formulaire, la sélection dans un calendrier ou les clics sur le CTA à l'intérieur de l'iframe se retrouve empilée sur elle-même dans un tout petit rectangle.

Le correctif le plus rapide est une extension Chrome gratuite qu'on maintient, Matomo Heatmap Helper. Elle force chaque iframe à une hauteur raisonnable pendant la capture (le vrai scrollHeight pour les embeds same-origin, au moins 800px pour les cross-origin) et restaure le style d'origine après. La suite de cet article explique comment faire la même chose sans extension, plus les correctifs permanents qui survivent à chaque future capture.

Comment corriger ça sans l'extension

Il existe un snippet console qui règle le problème juste avant chaque capture, et quelques correctifs permanents à embarquer avec l'embed. Choisis celui qui correspond aux contraintes de l'iframe que tu as en face.

Diagnostiquer ce qu'on a devant soi

Avant de changer quoi que ce soit, vérifie si l'iframe est same-origin (tu peux lire son contenu) ou cross-origin (tu ne peux pas). Ouvre la page dans Chrome, appuie sur F12 pour ouvrir DevTools, clique sur l'iframe dans le panneau Elements pour qu'il devienne $0, puis passe dans la Console :

js
// Colle dans la console après avoir sélectionné l'iframe dans le panneau 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 les deux chiffres ne concordent pas, c'est l'écart entre ce que le visiteur a vu et ce que Matomo va capturer. Si la deuxième ligne affiche cross-origin, la page parent ne peut pas lire le contenu de l'embed et la plupart des snippets ci-dessous vont l'ignorer. Force une hauteur minimale sur ceux-là à la place.

Redimensionner les iframes same-origin depuis la console

Si ton iframe est sur la même origine que la page (un formulaire auto-hébergé, une application interne embarquée dans ton site marketing, un produit cousin sur un sous-domaine qui partage ton origine via document.domain), le parent peut lire sa hauteur de contenu directement. Colle ça juste avant de déclencher la capture heatmap :

js
// Colle dans la console. Redimensionne chaque iframe same-origin à sa hauteur de contenu.
document.querySelectorAll('iframe').forEach(f => {
  try {
    const doc = f.contentDocument;
    if (doc) f.style.height = doc.body.scrollHeight + 'px';
  } catch (e) { /* cross-origin, skip */ }
});

C'est tout pour le same-origin. Le navigateur bloque l'accès à contentDocument sur les iframes cross-origin, donc chaque widget tiers est silencieusement ignoré. Relance le snippet de diagnostic pour confirmer que les hauteurs sont correctes, puis déclenche la capture.

Forcer une hauteur minimale sur les iframes cross-origin

Pour Cal.com, Calendly, YouTube, Stripe Checkout et tous les autres embeds tiers, la page parent ne peut pas lire le contenu de l'iframe. Tu peux quand même définir une hauteur suffisante depuis l'extérieur, ce qui est le mieux qu'on puisse faire sans la coopération du site embarqué :

js
// Modifie MIN_HEIGHT pour correspondre à ton embed le plus haut (ex. 800 pour Cal.com, 1200 pour les longs formulaires).
const MIN_HEIGHT = 800;
document.querySelectorAll('iframe').forEach(f => {
  if (f.getBoundingClientRect().height < MIN_HEIGHT) {
    f.style.height = MIN_HEIGHT + 'px';
  }
});

C'est une estimation, pas une mesure. Règle-la généreusement. Un iframe un peu trop grand laisse un espace vide sous le contenu, ce qui est inoffensif. Un iframe trop court coupe toujours le bas du formulaire. On part sur 800 pour les widgets de réservation et 1 200 pour les longs formulaires d'inscription, et on ajuste page par page si un embed spécifique en a besoin de plus.

Correctif permanent quand tu contrôles les deux côtés : postMessage

Si l'iframe vit sur un domaine que tu contrôles (ton propre sous-domaine, un produit cousin, un outil interne), la solution la plus propre est une petite poignée de main postMessage. La page enfant rapporte sa hauteur à chaque fois que le contenu se redimensionne, le parent écoute et l'applique. Fini les estimations.

Dans la page enfant embarquée :

js
// Ajoute dans la page enfant de l'iframe
const sendHeight = () => parent.postMessage(
  { type: 'iframe-height', height: document.body.scrollHeight },
  '*'
);
new ResizeObserver(sendHeight).observe(document.body);

Dans la page parent :

js
// Ajoute dans la page parent
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';
  });
});

Ça résiste au contenu chargé en lazy, aux échanges de polices et aux étapes de formulaires dynamiques. Ça améliore aussi l'expérience des vrais utilisateurs, parce que l'iframe n'a plus besoin de sa propre barre de défilement.

Utiliser le snippet d'embed officiel du fournisseur

Cal.com, Calendly et HubSpot embarquent tous un protocole de redimensionnement postMessage natif dans leurs snippets d'embed officiels. Si tu as copié-collé un simple <iframe src="..."> depuis une page de documentation ou un thread de support, remplace-le par le script drop-in du fournisseur. Ils ont déjà résolu ça pour toi.

Pour Cal.com, c'est le package @calcom/embed-snippet ou la balise <script> depuis leur embed builder. Pour Calendly, c'est le snippet d'embed inline sur calendly.com. Pour HubSpot, c'est le code d'embed de formulaire depuis l'onglet "Share" du formulaire. Les iframes nus pointant vers l'URL rendue sont le pattern courant qui casse ici, et c'est presque toujours une erreur de copier-coller plutôt qu'un choix délibéré.

iframe-resizer pour les embeds cross-origin arbitraires que tu contrôles

Si tu contrôles les deux côtés mais que tu ne veux pas écrire le protocole postMessage toi-même, le package npm iframe-resizer gère les cas limites (contenu lazy, polices qui chargent tard, bizarreries iOS). Deux scripts, un dans le parent, un dans l'enfant. Même effet que le snippet ci-dessus, avec plus de tests à travers les navigateurs.

Remplacer l'iframe quand c'est possible

Pour YouTube et Vimeo sur les pages trackées avec des heatmaps, remplace l'iframe par une image poster et n'insère l'iframe qu'au clic. Le visiteur obtient quand même la vidéo, et la heatmap capture une cible de clic propre en pleine taille au lieu d'un minuscule placeholder. lite-youtube-embed est la version canonique de ce pattern, et c'est aussi plus rapide pour les vrais utilisateurs parce que le lourd lecteur YouTube ne se charge que quand quelqu'un en veut vraiment.

Ça n'aide pas pour les widgets de réservation ou les iframes de paiement, où l'embed est l'interaction. Ça aide pour la vidéo, les timelines sociales, et tout ce qui n'est décoratif qu'avant le clic.

Pourquoi Matomo ne peut pas dimensionner tes iframes

Même cause racine que les versions polices et images de ce problème, avec une subtilité en plus. La capture d'écran de Matomo est un processus en deux temps. D'abord, le tracker sérialise ton DOM live en HTML et l'envoie. Ensuite, ton serveur Matomo re-rend ce HTML pour produire la capture d'écran que tu vois dans la vue heatmap. Pas de session navigateur, pas de cookies, pas de Referer pointant vers ton domaine. Juste un fichier HTML rendu à froid depuis une IP différente.

Pour les iframes, ce re-rendu capture ce que l'embed montre dans son état par défaut. Un iframe est un document séparé. Le navigateur empêche le parent de redimensionner automatiquement un iframe cross-origin, donc la hauteur que tu vois en production est celle que le code d'embed du fournisseur a négociée via postMessage au runtime, plus la hauteur CSS définie par ton équipe pour l'UX en direct. Le renderer Matomo ne fait pas tourner JavaScript assez longtemps pour que ces poignées de main aboutissent. Les iframes same-origin peuvent techniquement être mesurés, mais la plupart des équipes s'appuient sur une hauteur CSS définie pour l'UX en direct plutôt que pour une capture statique, donc le renderer se retrouve de toute façon avec un frame réduit par défaut.

Ce qui se casse concrètement

Quelques patterns qu'on croise sans cesse :

  • Des balises <iframe src="https://cal.com/..."> nus collés dans un CMS au lieu du script d'embed officiel de Cal.com. Le script officiel câble le postMessage. L'iframe nu ne le fait pas.
  • Des embeds inline Calendly où la hauteur n'est définie nulle part en CSS. Le snippet de Calendly la définit. Un iframe fait à la main ne le fait pas.
  • Des formulaires HubSpot et Marketo qui se chargent de façon asynchrone après que la page est interactive. Le formulaire peut faire 600px en production et 0px au moment de la capture, parce que le JavaScript du formulaire n'avait pas encore injecté les champs quand Matomo a sérialisé le DOM.
  • Des iframes YouTube et Vimeo au ratio 16:9 par défaut pour une colonne de 560 pixels de large, rendue par Matomo dans un viewport différent, qui finissent écrasés.
  • Des iframes Stripe Checkout et autres iframes de paiement qui se verrouillent intentionnellement sur un petit frame par défaut et refusent de s'étendre pour des raisons de sécurité. C'est le fournisseur embarqué qui décide de la hauteur. Pas toi.
  • Des iframes same-origin avec height: auto en CSS, que le navigateur calcule correctement au runtime mais que le serveur de rendu statique traite comme 0 parce qu'il ne fait pas tourner le layout pass assez longtemps.

Si ton iframe est réduit dans la heatmap, tu touches au moins un de ces cas. Les embeds de formulaires en touchent généralement deux.

C'est la même famille de problèmes qui casse les polices, les images, les conteneurs avec défilement et les en-têtes sticky dans la même capture d'écran. On a écrit un article plus détaillé sur les captures de heatmaps Matomo cassées qui couvre le reste, plus des articles séparés sur pourquoi les polices ne chargent pas et pourquoi les images ne chargent pas parce que ça revient presque aussi souvent.

Ce qu'on ferait concrètement

Si l'iframe vit sur ton propre domaine, implémente la poignée de main postMessage. Ça corrige la heatmap, fluidifie l'embed en direct, et tu arrêtes de te battre contre les décalages de mise en page pour de bon.

Si c'est un embed tiers, utilise le snippet officiel du fournisseur. Cal.com, Calendly et HubSpot embarquent un protocole de redimensionnement fonctionnel dans les leurs. La plupart des problèmes qu'on voit viennent de gens qui collent l'URL de l'iframe rendu au lieu du script d'embed.

Si ni l'un ni l'autre n'est accessible (chat tiers, iframes de paiement, widgets de réservation tiers qu'on ne peut pas modifier), l'extension Chrome Matomo Heatmap Helper est ce qu'on utilise sur les sites clients où on ne peut pas changer la stack. Elle force une hauteur raisonnable pendant la capture (vrai scrollHeight pour le same-origin, au moins 800px pour le cross-origin) et restaure le style d'origine après, sans affecter la page en direct. Gratuite, open source, code sur GitHub.

Martez est le projet plus large dont l'extension est issue. Il connecte Matomo avec Meta Ads et Google Ads pour que le ROAS, la CLV et l'attribution soient à côté de tes web analytics au lieu d'être dans un tableur séparé. Il est en bêta privée. Rejoins la liste d'attente si c'est pertinent pour toi.

La plupart du temps, c'est un simple échange de snippet fournisseur ou une poignée de main postMessage. Ça vaut le coup de le câbler une bonne fois.

Pourquoi les iframes s'affichent reduits dans ta heatmap Matomo (et comment y remedier) - Martez Blog