Nextアプリでhooks使ったFirebase Analyticsの実装例

# 本記事の目的
- Firebaseを導入したNextアプリ上で、Firebase Analyticsを使うときの具体的な実装例を示します。
- 今回はhooksを使って実装しました。
- あくまで1実装例なので、ベストな方法の紹介というわけではないです。
    - というかあまり考えずに実装したので、もっといい方法はたくさんあると思います。
- FirebaseやAnalytics自体の説明や、導入方法は説明しません。

# Firebase Analyticsを使ってやりたいこと
- 最近個人開発でサービスをリリースしたので、それがどのように使われているかを知るために、ユーザーのアクション(=イベント)を記録したい。
- webサービスなのでにイベントのユーザーユニーク性は気にしないが、一度リロードするまでは各イベントの記録は一回度ずつにしたい。（何度も同じ操作をしたとしても最初の一回だけ記録したい）
- logを取得するメソッドはpagesの中で実行することになるが、可能な限りpagesの中ではAnalyticsのための処理を意識したくない。

# 実際の実装
## ディレクトリ構成
```
./
└─src/
   ├─lib/
   │  └─firebase.ts
   ├─hooks/
   │  └─useAnalytics.ts
   └─pages/
      └─index.tsx
```

## firebase.ts
firebase.tsの責務は環境に合わせたfirebaseライブラリの初期化。
環境変数から必要なKEYなどをつかてfurebaseAppを初期化し、それをもとにdbやanlyticsのインスタンスをexportします。
今回analyticsを導入したサービスでは開発環境ではanalyticsを記録したくなかったので、production環境ではない時にはanalyticsインスタンスにはundefinedを代入してます。
ちゃんとやるなら、ここではenvを見てfireaseAppの初期化に必要なKEYを開発環境や検証環境のものに変更すべきです。

また、SSR時に`getAnalytics(app)`を実行するとwindowがundefinedという理由でクラッシュするので、それもチェックしています。
これはSSRがあるnextならではですね。


```firebase.ts
import { getAnalytics } from "firebase/analytics";
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const app = initializeApp(firebaseConfig);
const analytics =
  process.env.NODE_ENV === "production" && typeof window !== "undefined"
    ? getAnalytics(app)
    : undefined;
const db = getFirestore(app);
export { db, analytics };
```

## useAnalytics.ts
useAnalyticsの責務は
- イベントを網羅的に管理する
- analyticsインスタンスを利用したイベントを記録する処理
です。

これらをいろんなところで使いまわせるように共通化したのがuseAnalyticsです。

analyticsは`lib/firebase.ts`で初期化したものをimportしているので、ここでは開発環境なのか本番環境なのかは一切意識せず、具体的にイベントを記録する処理に集中できます。

ちなみに[Analyticsではイベントを記録する時にパラメータも一緒の保存できる](https://firebase.google.com/docs/analytics/events?hl=ja&authuser=0&platform=web)のですが、今回の実装ではそこは使わないので省いています。

useCallbackを使っているのはちょっとでも処理が軽く慣ればと思って使ってみました。

計測したいイベントはEventNameTypeで文字列のユニオン型で管理します。
ここに全てイベントが並ぶし、型チェックのおかげでタイポもしないので安心です。
もっと大規模なサービスの場合は多分このやり方だと破綻すると思うので別の方法を考える必要がありそうです。（パラメータも管理したいだろうし）

あとやり方によってはリロードが挟まっても同じイベントを2回記録しなくて済む方法もある気がします。

```useAnalytics.ts
import { logEvent } from "firebase/analytics";
import { useCallback, useState } from "react";
import { analytics } from "lib/firebaseApp";

export type EventNameType =
  | "input_profile_title"
  | "select_first_song"
  | "select_last_song"
  | "create_profile"
  | "click_share_twitter"
  | "copy_link"
  | "click_new_profile_button";

const useAnalytics = (eventName: EventNameType) => {
  const [isLogged, setIsLogged] = useState(false);

  const log = useCallback(() => {
    // NOTE: 一度リロードするまでは同じイベントは一度しか発火させない
    if (isLogged) return;
    console.log("logEvent:", eventName);
    setIsLogged(true);

    // NOTE: production環境以外の時にはanalyticsがundefnedである。開発中にはGAを計測したくないため。
    if (!analytics) return;
    logEvent(analytics, eventName);
  }, [analytics, isLogged]);

  return log;
};

export default useAnalytics;
```

## index.tsx
あとはイベントを記録したいpagesの好きところでuseAnalyticsをimportし、`useAnalytics("input_profile_title")`のように計測したいイベントの名前を引数で与えて取得したlog関数を好きなところで実行するだけです。
pagesからするとanalyticsの処理は全てhooksの向こう側に隠蔽されてるのでとてもシンプルですし、analytics側の処理を変えたくなってもpages側では気にする必要がありません。

```index.tsx
import useAnalytics from "hooks/useAnalytics";

const Home: NextPage = () => {
  const [titlePrefix, setTitlePrefix] = useState("");
  const logInputProfileTitle = useAnalytics("input_profile_title");
  
  const handleInputTitle = (event: ChangeEvent<HTMLInputElement>) => {
    logInputProfileTitle();
    setTitlePrefix(event.target.value);
  };

  return (
    <div>
      {/* 省略 */}
    </div>
  )
}
```

# 後書き
- こうやって記事に起こしてみると甘い部分が多いなと実感しますね。
    - それに気が付くことが記事を書く目的なのでヨシ！
- 今回は最小の労力でとりあえずfireaseのイベントだけ取得したかったので、そういう意味ではささっと実装できるこの方法は良いと言えるかもしれない。
- ちなみに今回Analyticsを導入したサービスは↓です。よければこっちの記事も読んでください〜

[【個人開発】推し曲を動的に生成したOGP付きで共有できるサービス「推し曲.com」を作りました](https://qiita.com/0ba/items/baf8415e0f1593f81cd1)


Twitterもやってるので、よければフォローお願いします。
→https://twitter.com/ObataGenta