Next.jsでCore Web VitalsをAmazon CloudWatchに送信してみる

この記事は、[ニフティグループ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/nifty)の19日目の記事です。
昨日は@shin27さんで「[Amazon SageMaker Feature StoreにKaggleのTitanicデータを登録してみた](https://zenn.dev/sk27/articles/d7704e3f5fb330)」でした。

# はじめに
今年も早いものでもう2週間を切ってしまいましたね。今年11月に[水樹奈々](https://www.mizukinana.jp/)さんがおめでたを公表したり、近頃は寒い日が続いたりで皆さんいかがお過ごしでしょうか。

私は、今年11月に[dアニメストア](https://anime.dmkt-sp.jp/animestore/)で配信が開始されたアニメ「[頭文字D](https://magazine.yanmaga.jp/c/initial_d)」に今更ながらハマったり、イベント「[五等分の花嫁展 MAKEOVER](http://www.gotobun-exhibition.com/)」に参加したりして、コロナ禍でイベントが軒並み中止になる状況でもプライベートはそれなりに充実していたかなと思っています。

そして2021年1月から放映される予定のアニメ「[五等分の花嫁∬](https://www.tbs.co.jp/anime/5hanayome/)」が待ち遠しいです。推しキャラは中野三玖です。

弊社内の話に移すと、ニフティではAWSやスクラム開発、フロントエンドフレームワークなどの勉強会が定期的に開催されていて、私も運営メンバー兼勉強メンバーとして精進しています。難しさもありますが、自分の中の知識をアップデートできる楽しさもあります！
ちなみに勉強会について少しでも気になった方や詳しい話を聞きたい方はぜひ説明会に来てみると良いかと！
[ニフティ株式会社 採用情報](https://recruit.nifty.co.jp/)

さて前置きが長くなりすぎたのでそろそろ本題に入りますが、今回はNext.jsの勉強も兼ねつつ、Next.jsでCore Web VitalsをAmazon CloudWatchに送信してみた話を書きます。

どうしてこのお題で執筆しようと思ったのかについて語っておくと、それは[Next.jsでCore Web VitalsをGoogle Analyticsに送信する記事](https://blanktar.jp/blog/2020/05/nextjs-google-analytics-web-vitals)をたまたま見つけたので、仮に送信先をAmazon CloudWatchに変更してみたらどうなるのだろうかと興味を持ったためです。

# 用語の説明
ここではこの記事のタイトルに登場する用語について簡単に説明します。
## Next.jsとは
オープンソースの[React](https://ja.reactjs.org/)フロントエンド開発Webフレームワークで、主に以下の機能を持ち合わせています。

- サーバーサイドレンダリング
- ReactベースのWebアプリケーション用の静的Webサイトの生成

公式サイトは[こちら](https://nextjs.org/)で、執筆当時の最新はバージョン10.0.3です。

## Core Web Vitalsとは
WebサイトやWebアプリケーションのパフォーマンスを計測するための仕組みで、Next.jsではバージョン9.4から導入されています。
[Blog - Next.js 9.4 | Next.js](https://nextjs.org/blog/next-9-4#integrated-web-vitals-reporting)

なおCore Web Vitalsについて詳細を書くと記事が長くなってしまうのでここでは書きませんが、FE Study #2というイベントでも話に上がっていましたので、気になる方は以下の資料を見てください。
[メタ・パフォーマンスチューニング](https://speakerdeck.com/yosuke_furukawa/metapahuomansutiyuningu)

## Amazon CloudWatchとは
DevOpsエンジニア、開発者、SRE、および ITマネージャーのために構築されたモニタリング/オブザーバビリティサービスで、公式サイトは[こちら](https://aws.amazon.com/jp/cloudwatch/)です。

# それでは早速チャレンジしてみましょう
今回は[Getting Started](https://nextjs.org/docs/getting-started)を参考にしながらアプリケーションを作成するところから始めます。

まずは適当な内容のページ`pages/index.js`を用意します。自己紹介を行うページでも何でも良いです。
次に[Core Web Vitalsを使うコード](https://nextjs.org/docs/advanced-features/measuring-performance)と[SDKを組み込むためのコード例]
(https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/sdk-code-samples.html)、そして[SDKドキュメント]( https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatch.html#putMetricData-property)とにらめっこしながら、以下の内容を`pages/_app.js`に書きます。

``` pages/_app.js
// import App from 'next/app'
var AWS = require('aws-sdk');

AWS.config.update({
  credentials: new AWS.Credentials(
    "AKIAIOSFODNN7EXAMPLE",
    "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  ),
  region: 'us-west-2'
});

// Create CloudWatch service object
var cw = new AWS.CloudWatch({apiVersion: '2010-08-01'});

export function reportWebVitals(metric) {
  const name = "mypage"
  var params = {
    MetricData: [
      {
        MetricName: 'PageAnalytics',
        Dimensions: [
          {
            Name: 'NextJSAPP',
            Value: 'Initial'
          },
        ],
        Unit: 'None',
        Value: 0.0
      },
    ],
    Namespace: 'Page/Analytics'
  };
  console.log("=======CW Before=======");
  console.log(params);

  switch (metric.name) {
    case 'FCP':
      console.log("=======FCP=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'LCP':
      console.log("=======LCP=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'CLS':
      console.log("=======CLS=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'FID':
      console.log("=======FID=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'TTFB':
      console.log("=======TTFB=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    default:
      break
  }

  console.log("=======CW After=======");
  console.log(params);
  cw.putMetricData(params, function(err, data) {
    if (err) {
      console.log("Error", err);
    } else {
      console.log("Success", JSON.stringify(data));
    }
  });
}

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
```

最終的なディレクトリは以下のようになります。

```
.
├── pages
│   ├── _app.js
│   └── index.js
├── .gitignore
├── package.json
├── README.md
└── yarn.lock
```

準備が整ったので実際に動かしてみましょう。

```
$ yarn add react react-dom next aws-sdk
$ yarn build
$ yarn start
```
実際に動かしてみましたが、Webブラウザに搭載されている開発者ツールにあるコンソールにも
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144887/ac3ef74f-bbe6-aaa2-a816-593672bc244d.png)

Amazon CloudWatchにも記録されていました。
![image03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144887/93645a18-2c6a-af18-80d0-0d87b530293e.png)

なお、今回は実装を簡単にするため、AWS認証情報をソースコード内に直接記述する方式にしていますが、GitHubのパブリックリポジトリに上げてしまうと[攻撃](https://dev.classmethod.jp/articles/accesskey-leak/)されるのであまり望ましくはありません。あらかじめ.gitignoreファイルに記載した.envファイルを使用するなどしてよりセキュアにしましょう。
また、認証情報を設定する際には、`AWS.config.loadFromPath('./credentials.json');`を記述する方法もありますが、[こちらのマニュアル](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-json-file.html)にも記載されている通り、対応していないケースがあるのでご注意ください。
さらに、[こちらの記事](https://qiita.com/motchi0214/items/d9e8cfc04326f3bb6d6c)にも記載されている通り、ソースコード内に認証情報を直接記述する方式には非推奨のものが存在しています。ご注意ください。

# おわりに
今回はNext.jsでCore Web VitalsをAmazon CloudWatchに送信してみた話を書きました。

パフォーマンスメトリクスを追っていくことはサービスやシステムの運用を行う上で欠かせないことの一つなので、この記事が参考になれば幸いです。

また送信したメトリクスをもとにして、Slack通知など何らかのアクションを行うLambda関数を発火させるのもきっと面白いかもしれません。

そしてこの記事を執筆していたときに似たようなことをやっている方を見つけました。ただこちらはサーバレスでしたね。
https://github.com/serverless-nextjs/serverless-next.js/issues/762

ここまで読んでいただきありがとうございました。明日は@D_Wさんの記事です。GitHub ActionsでStoryBookをデプロイする方法について執筆してくれるそうです。楽しみにお待ちください！