Closed10

Next.js の reportWebVitals を調べてみる

uttkuttk

reportWebVitals() とは

基本的には、Vercel が提供する Next.js Analytics というサービスで使用される関数です。

Vercel にデプロイしている場合は、設定無し( 関数の記述も無し )でエクスペリエンススコア( Experience Score ) の取集ができますが、設定すればセルフホスティングしているサービスにも Next.js Analytics を使用できるみたいです。[1]

また送信先を変更すれば、他のWebサービスなどとの連携も可能なようです。[2]
Next.jsを使ってパフォーマンス解析などをしたい場合は、他のライブラリなどを読み込まなくても使用できるため、候補として挙げても良い感じがします👀

脚注
  1. https://vercel.com/docs/concepts/analytics#self-hosted ↩︎

  2. https://nextjs.org/docs/advanced-features/measuring-performance#sending-results-to-analytics ↩︎

uttkuttk

使用方法

カスタム App ( pages/_app.[jsx|tsx] のこと ) 内で reportWebVitals() を export するだけで使用できます👇

pages/_app.tsx
import type { AppProps, NextWebVitalsMetric } from 'next/app'

// メトリクス を取得する
export function reportWebVitals(metric: NextWebVitalsMetric) {
  console.log(metric)
}

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

export default MyApp
uttkuttk

取得できる値

TypeScriptの型を分かりやすくしたら、以下のような感じ👇

実際の NextWebVitalsMetric 型の定義と違うことに注意!
type NextWebVitalsMetric =
  | {
      id: string;
      startTime: number;
      value: number;
      label: "web-vital";
      name: "FCP" | "LCP" | "CLS" | "FID" | "TTFB";
    }
  | {
      id: string;
      startTime: number;
      value: number;
      label: "custom";
      name:
        | "Next.js-hydration"
        | "Next.js-route-change-to-render"
        | "Next.js-render";
    };
uttkuttk

それぞれのプロパティについて

  • id : 現在のページのメトリクスに紐づいた一意な値
  • name : メトリクス名
  • startTime : 対象のメトリクスが最初に記録された時間( ミリ秒 )
  • value : メトリクスの値。メトリクスの種別によって値は変わる
    • 例:"TTFB" の場合は 72 , "FCP" の場合は 44918.30000000447 などのようになる
  • label : メトリクスの種別 ( web-vital または custom )
uttkuttk

WebVitals について

https://web.dev/vitals/

Web Vitals は、主に Google が提唱している Web パフォーマンス( ユーザーエクスペリエンス )の指標です。

具体的な要素として以下のようなものがあります👇

※ レイアウトシフト: 表示内容が予期しない時に変更されること ( 例: 広告などによって表示がズレるなど )

reportWebVitals() では、上記の情報を label を使って簡単に取得することができます👇

WebVitals のスコアを取得する例
export function reportWebVitals(metric: NextWebVitalsMetric) {
  if( metric.label === "web-vital" ) {
    // WebVitals のスコアを使った処理
  }
}

または、以下のように switch 文を使用することで、それぞれのメトリクスにアクセスできます👇

export function reportWebVitals(metric: NextWebVitalsMetric) {
  switch (metric.name) {
    case 'FCP':
      // handle FCP results
      break
    case 'LCP':
      // handle LCP results
      break
    case 'CLS':
      // handle CLS results
      break
    case 'FID':
      // handle FID results
      break
    case 'TTFB':
      // handle TTFB results
      break
    default:
      break
  }
}
uttkuttk

カスタムメトリクス( Custom Metrics ) について

Next.js では、WebVitals に加えて、ページがハイドレイト( hydrate )してレンダリングされるのにかかる時間を測定するいくつかの追加のカスタムメトリクスがあります。

具体的な要素は以下の通りです👇

  • Next.js-hydration:ページのハイドレイト( hydrate )の開始と終了にかかる時間 ( ミリ秒単位 )
  • Next.js-route-change-to-render:ルート変更後にページがレンダリングを開始するのにかかる時間 ( ミリ秒単位 )
  • Next.js-render:ルート変更後にページのレンダリングが完了するまでにかかる時間 ( ミリ秒単位 )

※ ハイドレイト( hydrate/hydration ): コンポーネントをレンダリングしてイベントハンドラーをアタッチするプロセスは、「ハイドレーション」と呼ばれます。( こちらのissue より引用 )

reportWebVitals() では、上記の情報を label を使って簡単に取得することができます👇

Custom Metricsのスコアを取得する例
export function reportWebVitals(metric: NextWebVitalsMetric) {
  if (metric.label === 'custom') {
    // Custom Metrics のスコアを使った処理
  }
}

もちろん、name を使って、それぞれのメトリクスにアクセスすることもできます👇

各メトリクスの処理方法の例
export function reportWebVitals(metric: NextWebVitalsMetric) {
  switch (metric.name) {
    case 'Next.js-hydration':
      // handle hydration results
      break
    case 'Next.js-route-change-to-render':
      // handle route-change to render results
      break
    case 'Next.js-render':
      // handle render results
      break
    default:
      break
  }
}
uttkuttk

他のサービスなどにメトリクスを送信する

relay 機能として使うと、任意の結果を分析エンドポイントに送信して、サイトの実際のユーザーパフォーマンスを測定や追跡できます👇

他のサービスにメトリクスを送る例
export function reportWebVitals(metric) {
  const body = JSON.stringify(metric)
  const url = 'https://example.com/analytics'

  // `navigator.sendBeacon()` が使用可能な場合はそちらを使用し、ダメな場合は `fetch()` で代用します
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url, body)
  } else {
    fetch(url, { body, method: 'POST', keepalive: true })
  }
}

navigator.sendBeacon(): Navigator.sendBeacon() - Web API | MDN を参照

参照

Google Analytics との連携については、以下のリポジトリが参考になります👇

https://github.com/vercel/next.js/tree/canary/examples/with-google-analytics

uttkuttk

Google Analyticsにメトリクスを送信する

以下のソースコードは、Google Analytics に WebVitals の値を送信する例です。

NEXT_PUBLIC_GA_TRACKING_ID には、Google Analytics のダッシュボードで取得できる Traking ID ( UA-**** みたいなヤツ ) を指定してください。以下の例では、.env.local ファイルに環境変数として定義しています。

pages/_app.tsx
import type { AppProps, NextWebVitalsMetric } from "next/app";

import "../styles/globals.css";

// Google Analytics に WebVitals を送信する
export function reportWebVitals(metric: NextWebVitalsMetric) {
  const { id, name, value, label } = metric;

  window.gtag("event", name, {
    event_label: id,
    event_action: name,
    non_interaction: true,
    value: Math.round(name === "CLS" ? value * 1000 : value),
    event_category: label === "web-vital" ? "Web Vitals" : "Next.js custom metric",
  });
}

export default function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";

const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_TRACKING_ID || "";

export default class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          {/* Global Site Tag (gtag.js) - Google Analytics */}
          <script
            async
            defer
            src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
          />

          <script
            id="gtag-init"
            dangerouslySetInnerHTML={{
              __html: `
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());
                gtag('config', '${GA_TRACKING_ID}', {
                  page_path: window.location.pathname,
                });
              `,
            }}
          />
        </Head>

        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}
このスクラップは2022/02/26にクローズされました