Next.js上に人気記事を表示する
 
 こんにちは。フナイです。 今回は掲題の通り、Google Analytics Data API (GA4) を利用して人気記事を表示していきます。
参考に出来る記事ないかな?と思いつつググりまくったものの Google Analytics Reporting API v4を利用した記事 しかヒットしませんでした。 「GA4 API」みたいな検索が良くなかったのかもしれません。 まあ、それなら、記事にしちゃえばいいやと思って今に至ります。
機能が半分ぐらい実装される所で、良さげな記事を見つけました(僕の検索力に問題があったようです)。
本記事の実装は、恐らく当ブログで利用されます。参考になれば幸いです。
Google Analytics Data API (GA4) の使い方
ライブラリの準備
今回はNext.js + TypeScriptで実装していきます。好きなパッケージマネージャでインストールしましょう。
npm install @google-analytics/data
# or
yarn add @google-analytics/data必要なライブラリはこれだけです。
API利用の準備
これだけだとまだAPIを実行することは出来ません。 APIを実行するために、各種サービスの準備をしていきます。
- Google Cloud Platform
- サービスアカウントの作成
- サービスアカウントの鍵の発行と設定
 
- Google Analytics
- プロパティのアクセス管理
 
サービスアカウントの作成
(プロジェクトの作成については、ここでは省略します。)
Google Cloud Platformのページからサービスアカウントを作成します。
 サービスアカウントのリストを開きます
サービスアカウントのリストを開きます
 サービスアカウントに作成するアカウントの詳細を入れます
サービスアカウントに作成するアカウントの詳細を入れます
 念のため閲覧者のみのロールを割り当ててアカウントの作成を完了します
念のため閲覧者のみのロールを割り当ててアカウントの作成を完了します
ここまででサービスアカウントの作成が完了します。簡単でしたね?
サービスアカウントから鍵の発行
ここでは、先の項で作成したサービスアカウントに紐づく鍵を発行します。
 サービスアカウントのキータブを開いて、新しい鍵を作成ボタンを押下します
サービスアカウントのキータブを開いて、新しい鍵を作成ボタンを押下します
 新しい鍵を作成ボタンを押下後、キーのタイプが選択できるので
新しい鍵を作成ボタンを押下後、キーのタイプが選択できるのでJSONを選択します
環境変数の設定
また今回はNext.jsのSSGを利用します。 ですのでcredential等のファイル形式ではなく、必要な値を環境変数として埋め込むようにします。
{
  "type": "service_account",
  "project_id": "aaa",
  "private_key_id": "bbb",
  "private_key": "-----BEGIN PRIVATE KEY-----\nccc\n-----END PRIVATE KEY-----\n",
  "client_email": "test-service-account@silver-adapter-374912.iam.gserviceaccount.com",
  "client_id": "ddd",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-service-account%40silver-adapter-374912.iam.gserviceaccount.com"
}JSONファイルがダウンロードされるので、以下の2つをメモっておいてください。
- client_email
- private_key
環境変数では以下として利用します。
GA_CLIENT_EMAIL=test-service-account@silver-adapter-374912.iam.gserviceaccount.com
GA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
ccc
-----END PRIVATE KEY-----"
# 合わせてプロパティIDも設定しておく
GA_PROPERTY_ID=xxxNext.jsではクライアント側にも見えるように NEXT_PUBLIC_* という指定が出来ますが、今回は利用しません。
APIキーが露出することによって、意図せずAPIが叩かれたり、情報が流出したりする可能性があります。
ここまで完了したらGoogle Cloud Platform側の設定は完了です。 Google Analyticsの設定に移ります。
プロパティのアクセス管理
先にGoogle Cloud Platformで作成したアカウントの権限をGoogle Anaylticsに登録します。
 Google Analyticsの管理ページをから「プロパティのアクセス管理」を押下します
Google Analyticsの管理ページをから「プロパティのアクセス管理」を押下します
 サイドバーを開いたら右上のプラスボタンを押下してアカウントの追加画面を開きます
サイドバーを開いたら右上のプラスボタンを押下してアカウントの追加画面を開きます
 役割とデータ制限の追加でアドレスを指定し、追加する
役割とデータ制限の追加でアドレスを指定し、追加する
このページでは以下の順に実行します。
- サービスアカウントのメールアドレスを入力する
- 先の test-service-account@silver-adapter-374912.iam.gserviceaccount.com
 
- 先の 
- 「新規ユーザーにメールで通知する」のチェックボックスを外す
- 「追加」ボタンを押下する
上記でGoogle Analytics側の設定が完了します。これでようやくAPIを叩く準備が整いました。
APIを実行する
以下の様にNode.jsでAPIを実行することが可能です。
import { BetaAnalyticsDataClient } from '@google-analytics/data';
const analyticsDataClient = new BetaAnalyticsDataClient({
  credentials: {
    client_email: process.env.GA_CLIENT_EMAIL,
    private_key: process.env.GA_PRIVATE_KEY?.replace(/\\n/g, '\n'),
  },
});
export const fetchPopularPosts = async (
  startDate: '7daysAgo' | '14daysAgo' | '30daysAgo',
  pageSize: number,
) => {
  const [response] = await analyticsDataClient.runReport({
    property: `properties/${process.env.GA_PROPERTY_ID}`,
    dateRanges: [
      {
        startDate: startDate,
        endDate: 'today',
      },
    ],
    dimensions: [
      {
        name: 'pagePath',
      },
    ],
    metrics: [
      {
        name: 'screenPageViews',
      },
    ],
    dimensionFilter: {
      filter: {
        fieldName: 'pagePath',
        stringFilter: {
          matchType: 5, //
          value: '^/(articles)/[0-9a-zA-Z\\-]+$',
        },
      },
    },
    limit: pageSize,
  });
  return response.rows?.map((row) => {
    console.log(row.dimensionValues[0], row.metricValues[0]);
  });
};取得した結果を出力すると以下のような形式で表示されます。
使い易い様に Object に詰めたりしましょう。
{ value: '/articles/aaa', oneValue: 'value' } { value: '44', oneValue: 'value' }
{ value: '/articles/ddd', oneValue: 'value' } { value: '38', oneValue: 'value' }
{ value: '/articles/ccc', oneValue: 'value' } { value: '24', oneValue: 'value' }
{ value: '/about', oneValue: 'value' } { value: '24', oneValue: 'value' }上記のデータから記事データのみでフィルタしたいのでdimensionFilter の部分に正規表現を入れます。
フィルタに利用する MatchType には FULL_REGEXP を利用しています。
namespace StringFilter {
  /** MatchType enum. */
  enum MatchType {
    MATCH_TYPE_UNSPECIFIED = 0,
    EXACT = 1,
    BEGINS_WITH = 2,
    ENDS_WITH = 3,
    CONTAINS = 4,
    FULL_REGEXP = 5,
    PARTIAL_REGEXP = 6,
  }
}実装
最終的な実装については添付しておきます。
コンポーネント側
おわりに
思ったより実装自体は難しくなかったですが、下準備が大変なので面倒くさいかもしれません。 この機能は既に以下のページで運用しています。実際に触ってみてください。

