Matomoヒートマップのスクリーンショットが壊れる原因と修正方法

Matomoのヒートマップスクリーンショットが壊れるのは、Cookie、フォント、CORSアクセスのない別サーバーでページが再レンダリングされるためです。各問題の原因と修正方法を解説します。

Matomoのスクリーンショットキャプチャは2段階で動いていて、その間で色々と壊れます。

まず、ユーザーがページにアクセスすると、MatomoのトラッキングJavaScriptがDOMをHTML文字列にシリアライズして(TreeMirrorというライブラリ経由)、Matomoサーバーに送ります。そのHTMLをMatomoのサーバー側でレンダリングして、ヒートマップの背景画像として使う仕組みです。

ここで問題になるのが、ページがまったく別のコンテキストで再レンダリングされること。MatomoサーバーにはCookieもセッションもないし、オリジンも違うし、JSフレームワークがページに何をしたかも知りません。あなたのブラウザ上で普通に動いていたものが、別のサーバーでHTMLをコールドレンダリングした途端に壊れるわけです。

この仕組みさえ頭に入れておけば、ヒートマップのスクリーンショット周りのバグはだいたい腑に落ちるはずです。

壊れたヒートマップスクリーンショットの見え方

よくある症状をいくつか挙げると:

  • ヒーロー画像がヒートマップだと壊れたアイコンになる(ブラウザでは普通に見えてるのに)
  • スクロールできる商品グリッドが最初の行しか出ない
  • 固定ナビゲーションがスクリーンショットでページコンテンツに重なってる
  • ブランドフォントがシステムのデフォルトフォントに化けてる
  • /assets/banner.webp みたいな相対パスのアセットがそもそも読み込まれない
  • SPAページがレンダリング途中でキャプチャされて、コンテンツが半分しかない

全部ちゃんとした技術的な原因があります。

CORSで画像やフォントがブロックされる

一番多い問題がこれ。ヒーロー画像や商品写真がヒートマップだと空のボックスや壊れたアイコンになるやつです。

ブラウザにはCookieやセッションがあるから画像は普通に読み込めるんですが、Matomoのサーバーが https://cdn.yoursite.com/hero.jpg を取りに行くと、CDNは知らないオリジンからのリクエストだと判断して弾きます。よくあるCORSの話です。

サーバー側の対処はシンプルで、CDNのレスポンスヘッダーに Access-Control-Allow-Origin: * を追加するだけ。MatomoのドキュメントのヒートマップCORS設定ガイドに詳細があります。

CDNの設定をいじれない場合は、Matomoがページをキャプチャする前にリソースをbase64データURIとしてDOMに直接埋め込む手があります。シリアライズされたHTMLに画像がインラインで入るので、サーバーが外部から何もフェッチしなくて済みます。srcsrcset、CSSの背景画像、SVG参照、ビデオのポスター画像、全部これで対応できます。

スクロールコンテナがコンテンツを切り取る

固定の高さにスクロールバーが付いたセクションがあると、ヒートマップのスクリーンショットには見えてる部分しか出ません。折りたたまれた先は全部消えます。

overflow: hiddenoverflow: auto が掛かってる要素は、レンダリング時の高さでコンテンツがクリップされます。MatomoはDOMをその時点の表示状態でシリアライズするので、キャプチャされたHTMLには完全な scrollHeight ではなく clientHeight しか反映されません。

対処としては、コンテンツコンテナで固定の高さを使わないこと。height の代わりに min-height を使って、スクロール可能なコンテンツがある要素から overflow: hidden を外しましょう。

固定ヘッダーやスティッキーヘッダーがページに重なる

スティッキーナビゲーションはブラウザだと問題ないのに、スクリーンショットだとコンテンツの上に被さって下が見えなくなります。

position: fixedposition: sticky はビューポート基準で配置されるんですが、サーバー側にビューポートなんてないので、そのままコンテンツの上に崩れ落ちてしまいます。

Matomoはスクリーンショットをレンダリングするときに <html> 要素へ matomoHeatmap クラスを付けてくれるので、キャプチャ時だけ効くCSSを書けます:

css
html.matomoHeatmap .your-sticky-header {
  position: relative;
  top: auto;
}

これでスティッキーヘッダーがスクリーンショットの時だけ通常フローに戻りますが、実際のユーザーには何も影響しません。Matomoのドキュメントに重なるヘッダーの修正キャプチャ時のカスタムスタイルシート適用のFAQがあります。

CSS追加が面倒なら、fixedやstickyのヘッダー・ナビを自動検出して position: relative に変換し、top/bottom/left/rightをクリアして、fixed要素にはレイアウトずれ防止用のプレースホルダーdivを差し込むアプローチもあります。キャプチャが終わったら全部元に戻します。

相対URLが動かない

画像やスタイルシートがサイト上では普通に見えるのに、クロスオリジンでもないのにMatomoのスクリーンショットで出てこない。最初は意味がわからない問題です。

原因は /images/banner.jpg のような相対URL。ブラウザではあなたのドメインに対して解決されますが、Matomoのレンダリングコンテキストだと /images/banner.jpg はMatomoのサーバー上のパスとして解決されます。当然そこにファイルはありません。

直し方は、全部絶対URLにするか、<head><base href="https://yoursite.com/"> タグを入れるか。それも厳しければ、キャプチャ前のスクリプトでドキュメント内の srchrefsrcset、CSSの background-image に含まれる相対URLを拾い上げて、ページのオリジンを使って絶対URLに書き換えることもできます。

カスタムフォントがシステムデフォルトになる

ブランドフォントが消えて、スクリーンショットにはサーバーのシステムフォントが出てきます。大抵ひどい見た目です。

@font-face ルールがCDNやあなたのサーバーのフォントファイルを参照していて、Matomoのサーバーがそれを取りに行くとCORSで弾かれるか、CDNがブラウザのUser-Agentを要求して失敗します。結果、デフォルトフォントにフォールバック。

対処:CORSヘッダーを適切に設定したサーバーでフォントをホストしてください。Google Fontsは最初からこれに対応していますが、セルフホストだと大抵対応していません。ヘッダーの変更が難しければ、フォントファイルをフェッチしてbase64データURIに変換し、そのデータURIを含む @font-face ルールを <head> 内の <style> タグに注入する方法もあります。シリアライズされたHTMLがフォント定義を自前で持つことになるので、どこから配信されても正しくレンダリングされます。

iframe、動画、SPA

いくつかエッジケースがあります。

iframe はCSSで指定したサイズと実際のコンテンツが合っていないことが多いです。同一オリジンのiframeなら contentDocument.body.scrollHeight で実際の高さを取ってサイズ調整できます。クロスオリジンの場合は、余裕を持ったフォールバックの高さを設定するしかありません。

自動再生動画 は再生の途中でキャプチャされて、ランダムなフレームが映ります。キャプチャ前に一時停止してフレーム0にシークしておけば、スクリーンショットには最初のフレームが入ります。

シングルページアプリケーション が一番やっかいです。React、Vue、Angularのアプリはコンテンツを動的に読み込むので、フレームワークのレンダリングが終わる前にMatomoがDOMを取ってしまうことがあります。URLがルーターの表示と食い違うケースも。万能な修正方法はなくて、MatomoのURL一致ルールをSPAルート用に設定して、MatomoのJS APIでレンダリング完了後にキャプチャをトリガーするのが現実的なやり方です。

Matomo Heatmap Helperで一括修正する

実際のサイトだと、上で書いた問題が複数同時に起きてるのが普通です。全部手で直そうとするとCDNの設定変更、URLの書き換え、CSSの手直し、カスタムスクリプトの作成と、けっこうな手間になります。

Matomo Heatmap Helperは、これら全部に対応する無料のオープンソースChrome/Firefox拡張機能です。GitHubでソースを公開しています

使い方はこんな感じ。拡張機能をインストールしたら、MatomoのURLとAPIトークンを入力して、有効にするサイトを選びます。対象ページでツールバーが出るので「Interactive」をクリック。修正が必要な要素(スクロールコンテナ、スティッキーヘッダー、画像が抜けてるセクションなど)をクリックしていきます。処理対象にはロックアイコンが付きます。

スクリーンショットボタンを押すと、拡張機能がまとめて処理を走らせます。クロスオリジンの画像とフォントをフェッチして埋め込み、制約された要素を展開してヘッダーの固定を解除、iframeと動画を処理、相対URLを絶対URLに変換してから、MatomoのスクリーンショットAPIを叩きます。キャプチャ後は変更が全部巻き戻されて、ページは元の状態に戻ります。

スクリーンショットを撮る前のチェック

確認しておくこと:

  1. ページが完全にレンダリングされているか(SPAコンテンツや遅延読み込みは特に注意)
  2. コンテンツをクリップしてるコンテナをロックしたか
  3. スティッキーヘッダーや固定ヘッダーがコンテンツに被さっていないか
  4. 主要な画像とフォントがちゃんと表示されているか
  5. スクリーンショットを撮って、検証が通るか
  6. ヒートマップビューで結果を目視確認したか

まとめ

スクリーンショットの問題はサイト自体のバグじゃありません。Matomoがページをシリアライズして別の場所で再レンダリングするときの副作用です。CDN、Webフォント、スクロールコンテナ、スティッキーヘッダーを使っているなら、どれかには必ず引っかかります。

一つ目の方法は、各問題をインフラ側で潰すこと。CORSヘッダーの追加、絶対URLへの統一、overflowコンテナの見直し、キャプチャ時のstickyポジション切り替え。サーバーをいじれるなら長期的にはこちらが正攻法ですが、時間はかかりますし、サーバー側だけでは綺麗に解決できない問題もあります。

もう一つは、Matomo Heatmap Helperでキャプチャ時にまとめて対処する方法。インタラクティブモードで修正が必要な箇所を指定すれば、本番コードをいじらずにクリーンなスクリーンショットが撮れます。