Matomo maakt screenshots in twee stappen, en precies daartussen loopt het mis.
Stap een: iemand bezoekt je pagina en Matomo's tracking-JavaScript serialiseert de DOM naar een HTML-string (via een library genaamd TreeMirror) en stuurt die naar je Matomo-server. Stap twee: de Matomo-server rendert die HTML tot de achtergrondafbeelding die je in de heatmapweergave ziet.
Waar het fout gaat: je pagina wordt opnieuw gerenderd in een totaal andere context. De Matomo-server heeft geen cookies, geen sessies, een andere origin, en weet niks van wat je JavaScript-frameworks na het laden met de pagina hebben gedaan. Alles wat werkte doordat het in een echte browser draaide met de inloggegevens van je domein, kan stukgaan zodra diezelfde HTML koud wordt gerenderd op een andere server.
Als je dit eenmaal doorhebt, vallen de meeste heatmap-screenshotbugs op hun plek.
Hoe kapotte heatmap-screenshots eruitzien
Dit zijn de problemen waar je tegenaan loopt:
- Hero-afbeelding laadt prima in de browser, maar is een kapot icoontje in de heatmap
- Een scrollbaar productoverzicht toont alleen de eerste rij, de rest is afgesneden
- Sticky navigatie overlapt de pagina-inhoud in de screenshot
- Merklettertypen zijn vervangen door generieke systeemstandaarden
- Assets met relatieve paden zoals
/assets/banner.webpladen helemaal niet - SPA-pagina's worden halverwege het renderen vastgelegd, met de helft van de content weg
Elk van deze problemen heeft een concrete technische oorzaak. Hieronder loop ik ze langs.
CORS blokkeert je afbeeldingen en lettertypen
Veruit het meest voorkomende probleem. Je hero-afbeelding, productfoto's of achtergrondafbeeldingen verschijnen als lege vakken of kapotte icoontjes in de heatmap-screenshot.
De reden: je afbeeldingen laden in de browser gewoon, want de browser heeft je cookies en sessiecontext. Maar zodra de Matomo-server https://cdn.jouwsite.nl/hero.jpg probeert op te halen, ziet de CDN een onbekende origin en weigert het verzoek. CORS, kortom.
Server-side los je dit op door Access-Control-Allow-Origin: * toe te voegen aan de response-headers van je CDN. Matomo beschrijft dit in hun heatmap-installatiehandleiding.
Heb je geen controle over de CDN of wil je de serverconfiguratie niet wijzigen? Dan kun je bronnen ook rechtstreeks in de DOM inbedden als base64 data-URI's, voordat Matomo de pagina vastlegt. Zo bevat de geserialiseerde HTML elke afbeelding inline en hoeft de server niks op te halen. Dat geldt voor src, srcset, CSS-achtergrondafbeeldingen, SVG-verwijzingen en video-posterafbeeldingen.
Scrollcontainers die je content afsnijden
Je hebt een sectie met een vaste hoogte en een scrollbalk. In de heatmap-screenshot wordt alleen het zichtbare deel gerenderd. Alles onder de vouw is weg.
Elementen met overflow: hidden of overflow: auto knippen content af tot hun gerenderde hoogte. Matomo serialiseert de DOM in zijn huidige visuele staat, dus de vastgelegde HTML weerspiegelt clientHeight, niet de volledige scrollHeight.
Zelf oplossen: vermijd vaste hoogtes op contentcontainers. Gebruik min-height in plaats van height, en pas geen overflow: hidden toe op elementen met scrollbare content.
Sticky en fixed headers die de pagina overlappen
Je sticky navigatie doet het goed in de browser, maar in de screenshot staat die gewoon bovenop de content en bedekt alles eronder.
position: fixed en position: sticky elementen worden gepositioneerd ten opzichte van de viewport. Server-side is er geen viewport, dus klappen ze in op de content eronder.
Matomo voegt een matomoHeatmap-klasse toe aan het <html>-element tijdens server-side screenshot-rendering. Daar kun je gebruik van maken om CSS-regels te schrijven die alleen tijdens het vastleggen gelden:
html.matomoHeatmap .your-sticky-header {
position: relative;
top: auto;
}Hiermee zet je de sticky header om naar de normale flow in de screenshot, zonder dat echte gebruikers er iets van merken. Matomo beschrijft dit in hun FAQ over overlappende headers in heatmaps repareren en aangepaste stylesheets toepassen tijdens het vastleggen.
Wil je geen extra CSS-regels onderhouden? De alternatieve aanpak is om header- en navigatie-elementen met fixed of sticky positionering te detecteren, ze om te zetten naar position: relative, hun top/bottom/left/right-waarden te wissen, en bij fixed elementen een placeholder-div in te voegen zodat de layout niet verspringt. Na het vastleggen draai je alles terug.
Relatieve URL's die niet meer werken
Sommige afbeeldingen en stylesheets laden prima op je site maar ontbreken in de Matomo-screenshot, ook al zijn ze niet cross-origin. Verwarrend, totdat je snapt wat er aan de hand is.
Je pagina gebruikt relatieve URL's zoals /images/banner.jpg. In de browser wordt dit opgelost ten opzichte van je domein. Maar in Matomo's renderingcontext wordt /images/banner.jpg opgelost ten opzichte van de Matomo-server. Dat bestand bestaat daar simpelweg niet.
Oplossing: gebruik overal absolute URL's, of voeg een <base href="https://jouwsite.nl/"> tag toe aan je <head>. Als dat allebei niet handig is, kan een pre-capture script het document doorzoeken op relatieve URL's in src-, href-, srcset- en CSS background-image-attributen en ze herschrijven naar absolute URL's op basis van de huidige origin.
Aangepaste lettertypen die als systeemstandaarden verschijnen
Je merklettertypen zijn weg. De screenshot toont het standaard systeemlettertype van de server, en dat is meestal niet fraai.
@font-face-regels verwijzen naar lettertypebestanden op CDN's of je eigen server. Wanneer de Matomo-server die bestanden probeert te laden, wordt het geblokkeerd door CORS of verwacht de CDN een browser user agent. De server valt terug op een standaardlettertype.
De fix: host lettertypen op een server met ruime CORS-headers. Google Fonts doet dit al, maar bij veel zelf gehoste lettertypen ontbreekt dat. Kun je de headers niet aanpassen? Dan kun je lettertypen ook direct in de pagina inbedden door de lettertypebestanden op te halen, om te zetten naar base64 data-URI's, en als @font-face-regels in <style>-tags in de <head> te injecteren. Zo draagt de geserialiseerde HTML zijn eigen lettertypedefinities mee en wordt het correct weergegeven, ongeacht waar het geserveerd wordt.
Iframes, video's en SPA's
Nog een paar randgevallen die het waard zijn om te weten.
Iframes hebben vaak vaste CSS-afmetingen die niet overeen komen met hun werkelijke content. Bij same-origin iframes kun je de werkelijke contenthoogte uitlezen via contentDocument.body.scrollHeight en het formaat aanpassen. Bij cross-origin iframes zit er weinig anders op dan een ruime fallback-hoogte instellen.
Automatisch afspelende video's worden vastgelegd midden in het afspelen, dus je krijgt een willekeurig frame te zien. Pauzeer ze en spoel terug naar frame 0 voordat je vastlegt, zodat de screenshot het eerste frame toont.
Single page applications zijn het lastigst. React-, Vue- en Angular-apps laden content dynamisch, en Matomo legt de DOM mogelijk vast voordat je framework klaar is met renderen. De URL komt misschien ook niet overeen met wat je router weergeeft. Er is geen wonderoplossing. Wat het beste werkt: configureer Matomo's URL-matchingregels voor je SPA-routes en gebruik Matomo's JS-API om de opname te triggeren nadat het renderen klaar is.
Alles in een keer oplossen met Matomo Heatmap Helper
De meeste echte websites hebben meerdere van deze problemen tegelijk. Ze allemaal handmatig oplossen betekent CDN-configuraties aanpassen, URL's herschrijven, CSS refactoren en aangepaste capture-scripts schrijven.
Matomo Heatmap Helper is een gratis, open source extensie voor Chrome en Firefox die dit allemaal voor je afhandelt. Broncode op GitHub.
Hoe dat er in de praktijk uitziet: je installeert de extensie, voert je Matomo-URL en API-token in, en kiest welke sites je wilt inschakelen. Op matchende pagina's verschijnt een werkbalk. Klik op "Interactive" en klik vervolgens op de elementen die gerepareerd moeten worden: scrollcontainers, sticky headers, secties met ontbrekende afbeeldingen. Ze krijgen een slotpictogram om aan te geven dat ze worden verwerkt.
Wanneer je op de screenshotknop drukt, doorloopt de extensie een vaste reeks stappen. Eerst worden cross-origin afbeeldingen en lettertypen opgehaald en ingebed, dan worden beperkte elementen uitgebreid en headers losgemaakt, iframes en video's afgehandeld, relatieve URL's omgezet naar absolute URL's, en Matomo's screenshot-API getriggerd. Na het vastleggen wordt elke wijziging teruggedraaid. Je pagina is weer precies zoals die was.
Voordat je een screenshot maakt
Even een checklist:
- Zorg dat de pagina volledig is gerenderd, vooral SPA-content en lazy-loaded secties
- Vergrendel containers die content afsnijden of uitgebreid moeten worden
- Check op sticky of fixed headers die content kunnen overlappen
- Controleer of je belangrijkste afbeeldingen en lettertypen zichtbaar zijn
- Maak de screenshot en bevestig dat de verificatie slaagt
- Open de heatmapweergave en check het resultaat voordat je begint met analyseren
Twee wegen vooruit
Deze screenshotproblemen zijn geen bugs in je site. Het zijn bijeffecten van het feit dat Matomo je pagina serialiseert en ergens anders opnieuw rendert. Elke site die CDN's, weblettertypen, scrollcontainers of sticky headers gebruikt, loopt hier vroeg of laat tegenaan.
Je kunt elk probleem op infrastructuurniveau aanpakken: CORS-headers toevoegen, absolute URL's gebruiken, overflow-containers refactoren, sticky positionering omzetten tijdens het vastleggen. Dat is de juiste langetermijnaanpak als je controle hebt over de server, maar het kost tijd en voor sommige problemen bestaat geen nette server-side oplossing.
Of je lost ze op het moment van vastleggen op met Matomo Heatmap Helper. Gebruik de interactieve modus om aan te geven welke delen van je pagina hulp nodig hebben, en je hebt schone screenshots zonder productiecode aan te raken.