Next.jsでGTM連携する方法と注意点

この記事は朝日新聞社 Advent Calendar 11日目の記事です。

# Next.jsでGTM連携する方法と注意点
Next.jsで作成されたサイトにGoogle Tag Manager（以後 GTMと呼びます）を導入してサイトのユーザー行動計測を行ったのですが、導入する際に公式ドキュメントに詳細な説明がなく、next/linkによるページ遷移が原因で導入に手こずってしまったので、実践した導入方法と注意点をご紹介します。

この記事では、GA4（Google Analytics4）とGTMの管理画面操作方法や設定については、Googleのマニュアルが充実しているので触れません🙏

## 動作環境
動作環境は以下です。
```
next 14.0.3
Pages Router
```

## 導入方法
基本的なPV（ページビュー）情報を計測するために必要な対応を紹介します。

サイトのPV計測など全てのページ共通で計測するために、[_app.tsx](https://nextjs.org/docs/pages/building-your-application/routing/custom-app)に実装します。
（詳細な解説は後述）

```typescript:pages/_app.tsx
import { useEffect } from 'react'
import Script from 'next/script';

// 環境変数として定義を推奨（サンプルコードなので記載）
const GTM_ID = 'GTM-XXX'

function MyApp({ Component, pageProps, router }) {
  useEffect(() => {
    const sendPageView = () => {
      if (window.dataLayer) {
        window.dataLayer.push({ event: 'pageview' });
      } else {
        console.warn(`GTM datalayer does not exist`);
      }
    };
    router.events.on('routeChangeComplete', sendPageView);
    return () => {
      router.events.off('routeChangeComplete', sendPageView);
    };
  }, [router.events]);

  return (
    <>
      {GTM_ID && (
        <Script
          id={GTM_ID}
          strategy="afterInteractive"
          dangerouslySetInnerHTML={{
            __html: `
            (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
              new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
              j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
              'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
            })(window,document,'script','dataLayer','${GTM_ID}');
          `,
          }}
        />
      )}
      <Component {...pageProps} />
     </>
  );
}
export default MyApp;
```

GTMが推奨しているのでスクリプト実行を無効にされた場合の対策としてnoscriptタグの内容を[_document.tsx](https://nextjs.org/docs/pages/building-your-application/routing/custom-document)の、bodyタグの直下に記載します。
```typescript:pages/_documnet.tsx
      <body>
        <noscript>
          <iframe
            src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
            height="0"
            width="0"
            style={{ display: 'none', visibility: 'hidden' }}
          />
        </noscript>
        ...
      </body>
```

# 解説
ここからは、はまった注意点や実装をしていて得た知見を記載します。

GTMでは、ページの変更をトリガーで検知してGAにデータを送信します。
このトリガーは、「ページビュートリガー」や「履歴の変更トリガー」などGTMで計測されるタイミングが決められているものと「カスタムイベントトリガー」といって開発者が明示的に起動するトリガーの2種類があります。

https://support.google.com/tagmanager/topic/7679108

従来のMPA（Multi Page Application）サイトの場合は「ページビュートリガー」を設定しておけばGTMがよしなに計測をしてくれていたのですがNext.jsアプリで[Link](https://nextjs.org/docs/pages/api-reference/components/link)コンポーネントやnext/routerを使ってページ遷移を実現している場合はページ遷移ごとにloadイベントが発火せず計測がうまくいきません。

そこで、解決方法として2つの方法を試しました。

1つ目の方法は、Next.jsアプリで_app.tsxに初期化処理のみ（サンプルコードのuseEffect部分は削除）を記載して「ページビュートリガー」と「履歴の変更トリガー」を組み合わせて計測する方法です。

https://support.google.com/tagmanager/answer/7679322?hl=ja&ref_topic=7679108&sjid=12646648799992125908-AP

この方法の利点としてはアプリケーションのコードに記載する内容は最低限でGTM管理画面上の設定で対応できることです。

トリガーとして履歴の変更を使う理由としてはNext.jsがページ遷移する際にHistory APIを利用しているためです。
しかし、この方法を検証していく際に、「ページビュートリガー」と「履歴変更トリガー」で二重計測をしないように除外設定を入れないといけない部分が多かったり、リロードやブラウザバックの動作が安定しなかったため採用しませんでした。
（SPAサイトが増えているので今後、GTMが対応してくれていくかも...）

2つ目の方法は、「ページビュートリガー」とrouter.eventsでページ遷移を検知して発火した「カスタムイベントトリガー」を組み合わせて計測する方法です。
この方法の利点は実行タイミングを開発者がコントロールできるところとイベントだけではなくカスタムバリューなど付加情報も簡単に付与できる点です。

```typescript:pages/_app.tsx
  useEffect(() => {
    const sendPageView = () => {
      if (window.dataLayer) {
        window.dataLayer.push({ event: 'pageview' });
      } else {
        console.warn(`GTM datalayer does not exist`);
      }
    };
    router.events.on('routeChangeComplete', sendPageView);
    return () => {
      router.events.off('routeChangeComplete', sendPageView);
    };
  }, [router.events]);
```
routerオブジェクトの説明にあるように`routeChangeComplete - Fires when a route changed completely`なタイミングで`pageview`というカスタムイベントを送信しています。

https://nextjs.org/docs/pages/api-reference/functions/use-router#routerevents

もしも、追加でパスを送りたい場合は以下のようにしてみるのも良いです。
```typescript
    const sendPageView = (url) => {
      if (window.dataLayer) {
        window.dataLayer.push({ event: 'pageview', page_path: url });
      } else {
        console.warn(`GTM datalayer does not exist`);
      }
    };
```

# 最後に
Experimentalですがvercel/next.jsがThird Party LibrariesとしてGoogleTagManagerコンポーネントを提供しています。Next.jsがサポートしてくれたらとても心強いので今後が楽しみです！

https://nextjs.org/docs/pages/building-your-application/optimizing/third-party-libraries

https://github.com/vercel/next.js/tree/canary/packages/third-parties