🚀

Google Ad Manager(GAM)でAstro製webサイトに広告を配信する

2024/01/14に公開

先日、個人のブログサイトをNext.jsからAstroへ移行しました。
https://ebifran-roadbike-blog.com/

もともとGoogle Ad Manager(GAM)を使ってブログサイト内にAdSense広告を配信していたため、Astroへ移行後もGAMの利用を続けることにしました。ここではAstroで開発したwebサイトにGAMを導入する手順について紹介します。

以前Next.jsにGAMを導入する手順を書きましたが、少し情報を整理して今回の記事だけを見れば導入できるようにまとめました。(GAMの詳細については割愛します)

なお、webサイトをNext.jsからAstroへ移行した結果や感想については、私が所属する会社のテックブログに記事を書きましたのでそちらも参考にしてください。

事前準備

開発作業に入る前に、以下の作業を済ませておきます。

GAMに広告にユニットを作成する

webサイトに表示する広告枠の数だけ、GAM上で広告ユニットを作成する必要があります。

私のブログサイトを例にすると、記事詳細ページには以下のような広告枠が存在します。

これらの広告枠に相当する広告ユニットをGAM上に作成していきます。ここでは広告ユニットの詳細な作成手順については割愛します。

すべての広告ユニットを作成すると以下のような感じになります。私のブログサイトは1つのページに最大4つの広告枠が存在するため、その分の広告ユニットを作成しています。

作成した広告ユニットに対してAdSenseを配信する場合、広告ユニットの設定画面より「
AdSense を利用して未販売の広告枠の収益を最大化します。」のチェックをONにします。(デフォルトでONになっているはず)

これでGAM上の設定は完了です。本来はオーダーや広告申込情報の設定も必要ですが、AdSenseを配信するだけなら以上の設定で問題ありません。

Google Publisher Tag(GPT)の型定義ファイルをインストール

GAMで広告を配信するには、Google Publisher Tag(以下、GPT)を使った広告配信処理をwebサイト上に実装する必要があります。

今後の実装を楽に進めるために、googleが公式に提供している型定義ファイルをインストールしておくことをおすすめします(必須ではありません)。

パッケージマネージャーにnpmをお使いであれば以下のコマンドでインストール可能です。

npm install --save-dev @types/google-publisher-tag

GPTを使った単純な実装の全体像

GPTはリファレンスサンプルがとても充実しているため、開発に入る前に一度目を通しておくことをおすすめします。

最もシンプルな例としては、webサイトのheadとbodyにそれぞれ以下のような実装をすることで広告が配信できるようになります。ここでは、ページ内の「top」「middle」「bottom」という3つの広告ユニットに対して広告を配信する場合を例にします。

まず、head内には以下のようなJavaScriptを実装します。(Astroの実装例ではなく、一般的なhtmlに実装する場合の例)

<!-- ① gpt.jsを読み込む -->
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
<script>
    window.googletag = window.googletag || {cmd: []};
    googletag.cmd.push(function() {
        // ② defineSlotでGAM上の広告ユニットとページ内の広告枠(divタグ)を関連付ける
        googletag.defineSlot('/【GAM ID】/top', 【topユニットのサイズ】, 'topユニットに対応するdivのID').addService(googletag.pubads());
        googletag.defineSlot('/【GAM ID】/middle', 【middleユニットのサイズ】, 'middleユニットに対応するdivのID').addService(googletag.pubads());
        googletag.defineSlot('/【GAM ID】/bottom', 【bottomユニットのサイズ】, 'bottomユニットに対応するdivのID').addService(googletag.pubads());

        // ③ single request モードを使用して広告を取得
        googletag.pubads().enableSingleRequest();
        googletag.enableServices();

        // ④ ページ内のdivタグに広告を表示
        // ※公式のサンプルではbody内でこのscriptを実行しているが、headで実行しても問題なく機能する
        googletag.display('topユニットに対応するdivのID');
        googletag.display('middleユニットに対応するdivのID');
        googletag.display('bottomユニットに対応するdivのID');
});
</script>

次に、body内の広告を表示したい箇所に以下のようなdivタグを設置します。CLSの指標を低下させないように、min-heightに最低限確保したい枠の高さを指定します。

<div id='topユニット用div ID' style='min-height: 250px;'>
</div>

<div id='middleユニット用div ID' style='min-height: 250px;'>
</div>

<div id='bottomユニット用div ID' style='min-height: 250px;'>
</div>

これでページが読み込まれた際に広告が表示されるようになります。

ただしこの例では以下に対応しないため、これらを実現したいのであればもう少し対応が必要です。(詳細は後述します)

  • 端末の画面幅によって配信される広告サイズを制御する(レスポンシブ広告)
  • viewportの範囲外の広告を遅延読み込みする など

なお、これらのタグのサンプルはGAMの広告ユニットの「タグ」より生成することができます。

AstroでGAMを利用するサンプル(単純な実装例)

上記を踏まえて、GAMを使った広告配信の単純なサンプル(レスポンシブ広告や遅延読み込みに対応しない)をAstroで実装してみます。

この場合はとても簡単で、以下のようなscriptをheadに実装しつつ、対応するdivタグをbodyに用意するだけで広告配信されます。

<script
  is:inline
  async
  src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
<script is:inline>
  window.googletag = window.googletag || { cmd: [] };
</script>
<script>
    googletag.cmd.push(function() {
        // 以下は前述した単純な実装例と同じ
        googletag.defineSlot('/【GAM ID】/top', 【topユニットのサイズ】, 'topユニットに対応するdivのID').addService(googletag.pubads());
        googletag.defineSlot('/【GAM ID】/middle', 【middleユニットのサイズ】, 'middleユニットに対応するdivのID').addService(googletag.pubads());
        googletag.defineSlot('/【GAM ID】/bottom', 【bottomユニットのサイズ】, 'bottomユニットに対応するdivのID').addService(googletag.pubads());

        googletag.pubads().enableSingleRequest();
        googletag.enableServices();

        googletag.display('topユニットに対応するdivのID');
        googletag.display('middleユニットに対応するdivのID');
        googletag.display('bottomユニットに対応するdivのID');
});
</script>

気をつけるのは、gpt.jsの読み込みとwindow.googletag = window.googletag || { cmd: [] };の一行をis:inlineを付与したscriptタグ内に記述することくらいです。こうすることでAstroがscriptの最適化をしなくなり、googletagのスコープが制限されてしまうことを防げます。

ページごとに広告の表示パターンを変更する

私のブログサイトでは、記事の詳細ページとそれ以外のページで広告の表示パターンを変えています。記事の詳細ページは他のページと異なり、目次の下に広告が1つ追加になっています。

これを実現するには、head内のgoogletag.defineSlotの呼び出しを各ページの広告表示パターンに併せて変更する必要があります。

いくつか方法はあると思いますが、私は以下のような実装で対応しました。

各ページからlayout側へ広告表示のパターンを渡せるようにする

layoutファイルを以下の様な感じで定義しておきます。

layout.astro
---
interface Props {
  // 一覧ページor記事詳細ページを表す文字列
  pageType: "list" | "detail";
}

const { pageType } = Astro.props;
---

<!doctype html>
<html lang="ja">
  <head>
    <script is:inline
            async
            src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
    <script is:inline>
        window.googletag = window.googletag || { cmd: [] };
    </script>
    <script>
        // ここにgptを実装
    </script>
  </head>
  <body>
    <slot />
  </body>
</html>

各子ページからは以下の様にlayoutに広告表示パターンを渡します。これで渡された表示パターンをもとに、layout側でgoogletag.defineSlotの呼び出しを変更するための下準備が整いました。

<Layout pageType="list">
    <!-- 一覧ページの表示内容 -->
</Layout>

<Layout pageType="detail">
    <!-- 記事詳細ページの表示内容 -->
</Layout>

customElementsを作成してサーバー変数をscriptに渡す

layout側では、前述の手順で受け取ったpageTypeをもとに以下の様にgoogletag.defineSlotの呼び出しを変更したいところですが、これはうまくいきません。

layout.astro
---
interface Props {
  // 一覧ページor記事詳細ページを表す文字列
  pageType: "list" | "detail";
}

const { pageType } = Astro.props;
---

<!doctype html>
<html lang="ja">
  <head>
    <script is:inline
            async
            src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
    <script is:inline>
        window.googletag = window.googletag || { cmd: [] };
    </script>
    <script>
	// この分岐は意図したとおりに機能しない
        if (pageType === "list") {
	    // 一覧ページ用の処理...
	} else {
	    // 記事詳細ページ用の処理...
	}
    </script>
  </head>
  <body>
    <slot />
  </body>
</html>

理由は、フロントマター内の変数はサーバー側で処理されるため、クライアント側で実行されるscriptタグ内に値を渡すことができないからです。(scriptタグ内のpageTypeはundefinedになる)

この問題は、customElementsを作成してdata属性経由で値を渡すという方法で解決できます。詳細は公式ドキュメントのフロントマター変数をスクリプトに渡すを参考にしてください。

私の場合は以下のようなcustomElementsを作成することで対応しました。

layout.astro
---
interface Props {
  // 一覧ページor記事詳細ページを表す文字列
  pageType: "list" | "detail";
}

const { pageType } = Astro.props;
---

<!doctype html>
<html lang="ja">
  <head>
    <script is:inline
            async
            src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
    <script is:inline>
        window.googletag = window.googletag || { cmd: [] };
    </script>
    <!-- 作成したcustomElementsのdata属性にサーバー変数を指定 -->
    <gpt-head data-pagetype={pageType}></gpt-head>
	  
    <script>
  
      // customElementsの定義
      class GptHead extends HTMLElement {
        constructor() {
          super();
	    
          // data属性の値を受け取る
          const pageType = this.dataset.pagetype;

          googletag.cmd.push(function () {
	        if (pageType === "list") {
		    // 一覧ページ用のGPT呼び出し...
	        } else {
		    // 記事詳細ページ用のGPT呼び出し...
	        }
          });
        }
      }
      customElements.define("gpt-head", GptHead);
    </script>
	  
  </head>
  <body>
    <slot />
  </body>
</html>

これでサーバー変数の値を見てクライアント側で処理を分岐させることが可能になりました。実際にはRecord<"list" | "detail", AdUnit[]>のような形で各ページごとの広告ユニットの情報を定義しておき、取得した広告ユニットをもとにGPTを実行するのが良いと思います。

レスポンシブ広告に対応する

例えば、私のブログサイトではページ上部の広告ユニット(上部横長バナー)に以下の4つのサイズを設定しています。

  • 320x50
  • 468x60
  • 728x90
  • 970x90

これは「上部横長バナー」に上記4つのサイズの広告が配信されることを許可しているだけにすぎません。このまま前述した実装例で実装を行うと、例えばスマホ端末に970x90の大きな広告が配信されてしまう可能性があります。

レスポンシブな広告配信を実現するには、googletag.defineSizeMappingを使用して、viewportの幅と配信する広告サイズを紐付ける必要があります。公式サンプルのAd Sizesでサンプルコードと実行結果が確認できるため、目を通しておくことをおすすめします。

例として、私のブログサイトのページ上部の広告は以下のようなGPTを実行することでレスポンシブ対応を行っています。

const topSlot = googletag.defineSlot('/【GAM ID】/top', 【topユニットのサイズ】, 'topユニットに対応するdivのID')

// addSize でviewportと広告サイズを紐づける(第1引数がviewport、第2引数が広告サイズ)
const mapping = googletag
    .sizeMapping()
    .addSize([992, 744], [
        [970, 90],
        [728, 90],
    ])
    .addSize([768, 576], [728, 90])
    .addSize([517, 387], [
        [468, 60],
        [320, 50],
    ])
    .addSize([0, 0], [320, 50])
    .build();

topSlot.defineSizeMapping(mapping);

この例では、ブラウザのviewportに応じて以下のように広告配信の制御が行われます。

  • viewportが517x387未満の場合
    320x50の広告を配信
  • viewportが517x387以上、768x576未満の場合
    468x60または320x50の広告を配
  • viewportが768x576以上、992x744未満の場合
    728x90の広告を配信
  • viewportが992x744以上の場合
    970x90または728x90の広告を配信

viewportの範囲外の広告を遅延読み込みする

googletag.enableLazyLoadを使用すると、ファーストビューに存在しない広告リソースを遅延読み込みできます。公式のデモで実行結果を確認できます。

引数に細かいパラメータを与えることで、viewportにどの程度接近したらリソースを読み込むかの制御が可能です。詳細は公式のリファレンスを参考にしてください。

まとめ

AstroにGAMで広告配信するための手順について紹介しました。

AstroはMPAのため、Next.jsと比較すると手軽に広告配信を実装できました。GAMに関する記事はあまり見かけないため、この記事が参考になれば嬉しいです。

Discussion