Next.jsでGoogle Analytics(gtag.js)のPageView送信

Next.js を使ったサイトかつ、 `import Link from 'next/link'` で画面遷移をする際にGoogle AnalyticsのPageViewを送信するシンプルな方法を書いておきます。

react-ga, next-ga を使わずに Hocでシンプルに行うやり方。

# 実装方法
## 環境
- next 9.1.7

## Hocを作成
**`GA_MEASUREMENT_ID` は書き換えてください。**

このhocを next.js ではどのディレクトリに置けばいいかは
https://react-file-structure.surge.sh/
を参考にしてください。

```jsx
import React, { Component } from 'react';
import Head from 'next/head';
import Router from 'next/router';

export default ComposedComponent => {
  return class withGA extends Component {
    static displayName = `withGA(${ComposedComponent.displayName})`;

    static async getInitialProps(ctx) {
      const pageProps = ComposedComponent.getInitialProps
        ? await ComposedComponent.getInitialProps(ctx)
        : {};

      return { ...pageProps };
    }

    componentDidMount() {
      Router.events.on('routeChangeComplete', () => {
        setTimeout(() => {
          window.gtag('config', 'GA_MEASUREMENT_ID', {
            page_title: window.document.title,
            page_location: window.location.href,
          });
        }, 0);
      });
    }

    render() {
      return (
        <>
          <Head>
            <script
              async
              src="https://www.googletagmanager.com/gtag/js?id= GA_MEASUREMENT_ID
            />
            <script
              dangerouslySetInnerHTML={{
                __html: `
                  window.dataLayer = window.dataLayer || [];
                  function gtag() { dataLayer.push(arguments); }
                  gtag('js', new Date());
                  gtag('config', 'GA_MEASUREMENT_ID');
                `,
              }}
            />
          </Head>
          <ComposedComponent {...this.props} />
        </>
      );
    }
  };
};
```

## _app.js にラップ

`pages/_app.js` に↓にようにラップします

```jsx
import React from 'react';
import App from 'next/app';

import withGA from 'your_path/withGA';

class CoreApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }
    return { pageProps };
  }

  render() {
    const { Component, pageProps } = this.props;
    return <Component {...pageProps} />;
  }
}

export default withGA(CoreApp);  // これをやりたいだけ

```

### 備考
componentDidMount で setTimeout入れているのは、 `window.document.title` がうまく取得できないためです。
いずれ改修されて不要になるかもしれません。
参考: https://github.com/zeit/next.js/issues/6025#issuecomment-467423149

下記のように console に出力しつつ、 A -> B -> C とページ遷移した際の C の送信で意味がわかります。

```jsx
componentDidMount() {
  // titleが１つ前の画面のtitileになっている（CのときにBのtitle）
  console.log("1", {
    page_title: window.document.title,
    page_location: window.location.href,
  });

  Router.events.on('routeChangeComplete', () => {
    // titleが１つ前の画面のtitileになっている（CのときにBのtitle）
    console.log("2", {
      page_title: window.document.title,
      page_location: window.location.href,
    });

    setTimeout(() => {
      console.log("3", {
        page_title: window.document.title,
        page_location: window.location.href,
      });

      window.gtag('config', 'GA_MEASUREMENT_ID', {
        page_title: window.document.title,
        page_location: window.location.href,
      });
    }, 0);
  });
}
```

# まとめ
「単純にPageViewを送信できればいいんだけどねー」という目的に対して
ググってもピンポイントでやりたいことの情報が見つからないのでまとめてみました。