Next.jsでGoogle Analyticsを使えるようにする

@Panda_Program

Next.jsでGoogle Analyticsを使えるようにする

**Next.jsとはVercelが作成しているReactのフレームワークです。**面倒な設定を書かなくてもすぐに使えるZero Configを標榜しており、実際にwebpackやTypeScriptと一緒にReactを書く際にも特別な準備は不要です。SSRにも対応しており、Reactで開発するならNext.jsかFacebook製のCreate React Appを使うのがスタンダードになってます。

私は実務でNext.jsを使っており、このフレームワークはとても便利だと思っています。私はNext.jsの大ファンなので、Reactでの開発時にNext.jsを使う現場が増えるといいなと思って記事を書いています。

関連記事: Next.js + esa.io + VercelでJAMStackな爆速ブログを構築する

**この記事では、Next.jsでReactアプリケーションを作成する時に、Google Analytics(以下、GA)の設定をする方法をご紹介します。**一通り設定した後、TypeScript化していきます。

基本的にはNext.jsのExampleを参考にしています。ただ、実務で使うとこれだけでは足りないところがあるので、記事内では実務への橋渡しとなるような内容を盛り込んでいます。

なお、Google Analyticsのアカウント取得方法やスニペットの取得方法は記載していません。

Google AnalyticsのIDを.envに記述する

まず、ルートディレクトリに.envファイルを作成し、GAのIDを.envに記述します。

.env
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=UA-SOME_ANALYTICS_ID-1

Next.jsでは、.envに記述した環境変数をprocess.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_IDで取得できます。この値はビルド時に注入されます。これが最初の設定です。

NEXT_PUBLIC_という接頭辞をつけると、ブラウザにも露出する値になります。

GAイベントを発火させる関数を作成する

次に、GAイベントを発火させる関数を作成します。関数はsrc/lib/gtag.jsというファイルに記述していきます。

src/lib/gtag.js
export const GA_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID || ''

// IDが取得できない場合を想定する
export const existsGaId = GA_ID !== ''

// PVを測定する
export const pageview = (path) => {
  window.gtag('config', GA_ID, {
    page_path: path,
  })
}

// GAイベントを発火させる
export const event = ({action, category, label, value = ''}) => {
  if (!existsGaId) {
    return
  }

  window.gtag('event', action, {
    event_category: category,
    event_label: JSON.stringify(label),
    value,
  })
}

このファイルではgtagというGAのメソッドをラップする関数を作ります。

pageviewという関数でページビューを送信するには、configコマンドを使用します。引数でパスを受け取り、page_pathでURLのパスを送信します。

eventという関数でGAイベントを送信する関数を記述します。例えば、DOMのクリックイベントやSubmitのイベントなどを取得する時に使います。

なお、GAに関する詳しい内容はGoogleの公式ドキュメントをご覧ください。

_app.jsにGAのスクリプトを書き込む

_app.jsは全てのページで共通のHTMLを書くコンポーネントです。この_app.jsにGA用のscriptタグを記述します。

pages/_app.js
import Head from "next/head";
import React from 'react'

import { GA_ID, existsGaId } from '../src/lib/gtag'

const App = ({ Component, pageProps }) => {
  return (
    <>
      <Head>
        {/* Google Analytics */}
        {existsGaId && (
          <>
            <script async src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`} />
            <script
              dangerouslySetInnerHTML={{
                __html: `
                  window.dataLayer = window.dataLayer || [];
                  function gtag(){dataLayer.push(arguments);}
                  gtag('js', new Date());
                  gtag('config', '${GA_ID}', {
                    page_path: window.location.pathname,
                  });`,
              }}
            />
          </>
        )}
      </Head>

      <Component {...pageProps} />
    </>
  )
}

export default App

これで Google Analytics の JS を読み込むことができました。

Next.js v11 の Script コンポーネントを利用してリファクタする

Next.js の v11 から、Script コンポーネントが導入されました。 このコンポーネントを用いると、外部の JS を読み込むタイミングを簡単に制御できます。

ここでは、Script タグを使って Google Analytics の JS をサイトの実行に必要なスクリプトを読み込んでページがインタラクティブになった後に読み込むことで、ページが操作可能になるまでの時間を短縮する書き方を紹介します。

その恩恵を端的に書くと、Lighthouse でのスコアが上昇し、SEO に好影響を与えます。

src/components/GoogleAnalytics.js
import Script from 'next/script'
import { existsGaId, GA_ID } from '../lib/gtag'

const GoogleAnalytics = () => (
  <>
    {existsGaId && (
      <>
        <Script defer src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`} strategy="afterInteractive" />
        <Script id="ga" defer strategy="afterInteractive">
          {`
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag('js', new Date());    
              gtag('config', '${GA_ID}');
          `}
        </Script>
      </>
    )}
  </>
)

export default GoogleAnalytics

GoogleAnalytics コンポーネントはこれだけです。先程_app.jsに書いたものを切り出しただけです。

あとは_app.jsでこのコンポーネントを呼び出すだけです。

pages/_app.js
import React from 'react'

import GoogleAnalytics from '../src/components/GoogleAnalytics'

const App = ({ Component, pageProps }) => {
  return (
    <>
      <GoogleAnalytics />

      <Component {...pageProps} />
    </>
  )
}

export default App

これで Google Analytics の読み込みタイミングを遅らせることができました。

_app.jsにPVをカウントするイベントを記述する

Next.js製のサイトはSPAであるため、ページを遷移するときにJavaScriptでURLを書き換えます。その際、Google Analyticsはアクセスした最初のページしかページビュー測定のイベントを送信しません。つまり、ユーザーがサイト内を回遊したときの各ページのPVを測定できないのです。

この問題は、Next.jsのRouterを使えば解決できます。RouterのURL書き換えが完了した時に発火するrouteChangeCompleteイベントのコールバックとしてpageview関数を設定します。

これをpages/_app.jsに記述します。

pages/_app.js
import { useRouter } from "next/router";
import React, { useEffect } from 'react'

import { existsGaId, pageview } from '../src/lib/gtag'
import GoogleAnalytics from '../src/components/GoogleAnalytics'

const App = ({ Component, pageProps }) => {
  const router = useRouter()

  useEffect(() => {
    if (!existsGaId) {
      return
    }

    const handleRouteChange = (path) => {
      pageview(path)
    }

    router.events.on('routeChangeComplete', handleRouteChange)

    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])

  return (
    <>
      <GoogleAnalytics />

      <Component {...pageProps} />
    </>
  )
}

export default App

これで、ページ遷移時のPVイベントをGAに送信できました。

_app.jsの記述は長くなりがちなので、Custom Hooks に切り出しておくと便利です。

src/hooks/usePageView.js
import { useEffect } from 'react'
import { useRouter } from "next/router";

import { existsGaId, pageview } from '../lib/gtag'

export default function usePageView() {
  const router = useRouter()

  useEffect(() => {
    if (!existsGaId) {
      return
    }

    const handleRouteChange = (path) => {
      pageview(path)
    }

    router.events.on('routeChangeComplete', handleRouteChange)

    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])
}

_app.jsは以下のようにリファクタリングできます。

pages/_app.js
import React from 'react'

import usePageView from '../src/hooks/usePageView'
import GoogleAnalytics from '../src/components/GoogleAnalytics'

const App = ({ Component, pageProps }) => {
  usePageView() // 追加

  return (
    <>
      <GoogleAnalytics />

      <Component {...pageProps} />
    </>
  )
}

export default App

GAイベントをReactコンポーネントに設定する

**では、実際にReactコンポーネントでGAイベントの設定をしましょう。**以下ではContactコンポーネントでのボタンをクリックした時に、inputタグに入力されたメッセージをGAに送信します。

src/components/Contact.js
import React from 'react'
import Layout from './Layout'

import * as gtag from '../lib/gtag'

const Contact = () => {
  const [message, setMessage] = React.useState('')

  const handleInput = e => setMessage(e.target.value)
  const handleSubmit = e => {
    e.preventDefault()

    gtag.event({
      action: 'submit_form',
      category: 'Contact',
      label: message,
    })

    setMessage('')
  }

  return (
    <Layout>
      <h1>This is the Contact page</h1>
      <form onSubmit={handleSubmit}>
        <label>
          <span>Message:</span>
          <textarea onChange={handleInput} value={message} />
        </label>
        <button type="submit">submit</button>
      </form>
    </Layout>
  )
}

export default Contact

今回はhandleSubmitの中でeventを発火しています。同様にclickイベントならhandleClickの中に、onChangeイベントならhandleChangeの中にevent`関数を記述します。Reactでは取得したいイベントに応じて、GAイベントを柔軟に記述できます。

ここまででNext.jsでGoogle Analyticsを使うための設定ができました。

より詳しく知りたい方は ここまでのコードを反映したサンプルレポジトリ をご覧ください。

以下では、より実務に即した内容をご紹介します。

TypeScript対応をする

実務ではReactとTypeScriptの環境で開発している方も多いと思います。そのような方のために、Next.js + Google Analytics + TypeScriptの対応方法をご紹介します。

windowからGAイベントのプロパティを使うために型ファイルをインストールする

Next.jsでTypeScriptを使えるようにすると、ルートディレクトリにnext-env.d.tsというファイルが作成されます。これはNext.jsでTSを使うなら必須のファイルで、削除してはいけません。

前の章lib/gtag.jswindow.gtag()をラップする関数を作りましたね。

gtag.jsgtag.tsに書き換えると、windowオブジェクトにgtagというプロパティは存在しないという意味のエラーが表示されます。

TS2339: Property 'gtag' does not exist on type 'Window'.

この型エラーを消すために、Google Analytics 用の型ファイルをインストールしましょう。

$ npm i -D @types/gtag.js

これでgtag.tsでエラーが出なくなりました。

発火させるイベントを型で管理する

TypeScriptに対応したため、gtagをラップする関数に型をつけていきます。

まず、gtag.jsの拡張子をgtag.tsに変更します。そして、Google Analytics のイベントの型を作成します。

src/lib/gtag.ts
type ContactEvent = {
  action: 'submit_form'
  category: 'contact'
  label: string
}

type ClickEvent = {
  action: 'click'
  category: 'other'
  label: string
}

export type Event = ContactEvent | ClickEvent

型の作成は必須ではありません。

ただ、型で管理するとイベント設定時のスペルミスや値が undefined になるミスを未然に防ぐことができる上に、後からの仕様変更に強くなるためこのように書くことをお勧めしています。

こうすることでEvent.jsがサイト全体のGAイベントのドキュメント代わりになります。

数が増えると管理が大変なので、yml などでイベントの定義を PM に書いてもらい、TypeScript の型を自動生成するのがベストだとは思います。

次にwindw.gtagをラップする event 関数と、pageview関数の引数に型をつけます。

src/lib/gtag.ts
// ...
export type Event = ContactEvent | ClickEvent

export const event = ({action, category, label}: Event) => {
  if (!existsGaId) {
    return
  }

  window.gtag('event', action, {
    event_category: category,
    event_label: JSON.stringify(label)
  })
}

export const pageview = (path: string) => {
  window.gtag('config', GA_ID, {
    page_path: path,
  })
}

これで型をつけることができました。

モジュールごとに分割せず一ファイルにまとめる

これまで、gtag や hooks、GoogleAnalytics コンポーネントなど役割に応じてファイルを分割してきました。

しかし、全て Google Analytics に関するモジュールであるため、gtag.tsxの1ファイルにまとめてしまう方が管理が簡単かもしれません。

以下では、これまで書いてきたモジュールを1ファイル内にまとめておくコードを記載します。また、Event 型を実践用に少し書き換えています。

src/lib/gtag.tsx
import { useRouter } from 'next/router'
import Script from 'next/script'
import { useEffect } from 'react'

export const GA_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID || ''

// IDが取得できない場合を想定する
export const existsGaId = GA_ID !== ''

// PVを測定する
export const pageview = (path: string) => {
  window.gtag('config', GA_ID, {
    page_path: path,
  })
}

// GAイベントを発火させる
export const event = ({ action, category, label, value = '' }: Event) => {
  if (!existsGaId) {
    return
  }

  window.gtag('event', action, {
    event_category: category,
    event_label: label ? JSON.stringify(label) : '',
    value,
  })
}

// _app.tsx で読み込む
export const usePageView = () => {
  const router = useRouter()

  useEffect(() => {
    if (!existsGaId) {
      return
    }

    const handleRouteChange = (path: string) => {
      pageview(path)
    }

    router.events.on('routeChangeComplete', handleRouteChange)
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])
}

// _app.tsx で読み込む
export const GoogleAnalytics = () => (
  <>
    {existsGaId && (
      <>
        <Script defer src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`} strategy="afterInteractive" />
        <Script
          defer
          dangerouslySetInnerHTML={{
            __html: `
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag('js', new Date());    
              gtag('config', '${GA_ID}');
            `,
          }}
          strategy="afterInteractive"
        />
      </>
    )}
  </>
)

// イベントを型で管理
type ContactEvent = {
  action: 'submit_form'
  category: 'contact'
}

type ClickEvent = {
  action: 'click'
  category: 'other'
}

export type Event = (ContactEvent | ClickEvent) & {
  label?: Record<string, string | number | boolean>
  value?: string
}

よければ参考にしてみてください。

まとめ

Next.jsでGoogle Analyticsを使えるようにした上で、TypeScriptに対応しました。

Next.jsを本番環境で使用する場合、Google Analyticsは必須です。Google Analyticsを使ってユーザー行動を取得し、プロダクトの改善にぜひ役立ててください。

日本でNext.jsが現場で使われるケースが増えることを願っています。

Happy Coding 🎉

パンダのイラスト
パンダ

記事が面白いと思ったらツイートやはてブをお願いします!皆さんの感想が執筆のモチベーションになります。最後まで読んでくれてありがとう。

  • Share on Hatena
  • Share on Twitter
  • Share on Line
  • Copy to clipboard