Github PagesでReactのRouter（ページ遷移）を使い、GAに正しく認識させる方法

# はじめに
下記の要件を満たしたく、その方法を書きます。
- Github Pagesを使う
  - 無料の静的ホスティングサービスを探して
- ページ遷移がある
- GAでページごとのカウントを取りたい

# 課題の発生(認識)と、やっつけていった経緯
## 課題１：BrowserRouterだと404エラー
まず、ページ遷移を`react-router-dom`の`BrowserRouter`で実装してGithub Pagesに置いたところ、`https://sample.github.io/repo1/page1/`にアクセスすると、Page Not Found(404)が出てしまいました🔍

原因は、`BrowserRouter`は`https://sample.github.io/repo1/page1/index.html`をサーバーに問い合わせてしまうから。そしてそれは存在しないから。

### 課題１の対策
対策として、`react-router-dom`の`HashRouter`を使いました。その結果URLは、`https://sample.github.io/repo1/#/page1/`のようになり、そこにアクセスしても、サーバーには問い合わせず表示され、課題は解消しました。

## 課題２：GAの設定を`index.html`に入れたがいいのか？
Reactのベースとなる`index.html`のヘッダーに、GAの初期設定で言われるがまま、`gtag.js`をコピペしました。GAにも認識され、よかったよかった。

ちなみに埋め込む`gtag.js`と呼ばれるコードは下記。

```html:index.htmlのヘッダーの中
    <!-- Google tag (gtag.js) -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-xxxxxxxxxx"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());

      gtag('config', 'G-xxxxxxxxxx', {
        'anonymize_ip': true,
      });
    </script>
```

・・・ところでふと湧いた疑問。SPA(Single Page Application)の`index.html`に書いたら、**Single Page** でしかカウントされないのでは？⇒ YES💀

### 課題２の対策
ページ遷移ごとに、上のコードが動いてもらわないといけないので、`HashRouter`の中に`<PageTracker />`を入れて、ページ遷移ごとに強制的に`usePageTracking()`を動かさせる。

```TypeScript:App.tsxの一部
// usePageTrackingを使うための中間コンポーネント
const PageTracker = () => {
  usePageTracking();
  return null;
};

export default function App() {
  return (
    <HashRouter>
      <PageTracker />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </HashRouter>
  )
}
```

```TypeScript:usePageTraking.ts（課題３で示すようにバグがあります）
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

declare global {
  interface Window {
    gtag?: (...args: any[]) => void;
  }
}

const usePageTracking = () => {
  const location = useLocation();

  useEffect(() => {
    if (window.gtag) {
      const pagePath = location.pathname + location.search;
      window.gtag("config", "G-xxxxxxxxxx", {
        page_path: pagePath,
        anonymize_ip: true,
      });
    }
  }, [location]);
};

export default usePageTracking;
```
`index.html`だと１回しか呼び出されないけど、こうしておけば、ページ遷移のたびに呼び出されて安心。（とこの時は思った）

## 課題３：GAでページごとにカウントされていない（バグ）
GAの集計画面を見ていると、どうやらトップページしかカウントされていない。初日だけかと思って数日見てみても、やっぱりそう🤔

GAへ「このページが見られたよー」と`gtag()`を使って通知する、"このページ"が正しくありませんでした。

`react-router-dom`の`useLocation()`の返す値が、`BrowserRouterを使う場合`と`HashRouter`で、下記のように変わることがポイント。

```TypeScript:BrowserRouterを使う場合
// https://example.com/about?ref=twitterへアクセス
location = useLocation();
// location.pathname === "/about"
// location.search   === "?ref=twitter"
// location.hash     === ""
```
```TypeScript:HashRouterを使う場合
// https://example.com/#/about?ref=twitterへアクセス
location = useLocation();
// location.pathname === "/"
// location.search   === ""
// location.hash     === "#/about?ref=twitter"
```

つまり`HashRouter`を使うなら、`location.hash`を読み解いて`"/about"`を取り出さないといけないんです。

私が課題２の解として書いた`location.pathname + location.search`は、`BrowserRouter`ならいいけど、`HashRouter`だとどのページでも、`"https://example.com/"`になります🤣

### 課題３の対策
```TypeScript:usePageTraking.tsの一部（バグフィックス版）
const usePageTracking = () => {
  const location = useLocation();

  useEffect(() => {
    if (window.gtag) {
      const pagePath = 
        location.hash.replace(/^#/, '')  // HashRouter 対応
      window.gtag("config", "G-xxxxxxxxxx", {
        page_path: pagePath,
        anonymize_ip: true,
      });
    }
  }, [location]);
};
```

この結果、`location.hash`の値`"#/about?ref=twitter"`は加工され、`/about?ref=twitter`になり、`gtag()`へ渡っていくことになります。

なお、`?`以降の`?ref=twitter`は、**消してはいけません**。

私が今回使うかどうかはまだ未定ですが、**UTMパラメータ**というものをURLに入れる可能性があるためです。`https://example.com/#/about?utm_source=Google`などと、どこから来たかという情報も含めてURLを作って、例えばGoogle広告に出すとかすると、Googleから来たということを**GAで分析できる** ようになります。ここの細かい話は書きませんが、GAで使う場面がよくあるということで、基本的に消してはいけない。

以上の対応で、GAに正しく認識させることができるようになります。

# まとめ
Github Pages、React、GAと１つ１つはすごくたくさんの情報があって、単独で問題があっても簡単に解決できますが、それが **組み合わさる** と、情報が減るし、微妙に違う問題の解決策を誤って実施してしまうとか変な問題が起きたりしますね。 **その点はChatGPTでも同じ** で、組み合わせの条件を１つ伝え忘れちゃうと誤った解決策を提示されたりするので、使い方は気を付けないといけません。

課題をクリアするとまた新しい課題が出てきて、課題はいつまで続くのかわからない。それをやっつけ続けて、１つずつ解決してるんだと言い聞かせながら進み、ようやくゴールが見える。
これって抽象度を上げて考えてみると、人生そのものだなと思いました。失敗はない・過程なんだという話、これなんだ。がんばりましょう。

では、よき課題解決ライフを！