GA4から取得したデータをmicroCMSに送信して、人気記事ランキングを実装する

投稿日:

更新日:

前提条件

  • Next.js v14.0.2
    • App Router
    • Route Handlers
  • Google Analytics 4
  • microCMS
    • microcms-js-sdk v2.7.0

目的

Next.jsで構築し、ヘッドレスCMSにmicroCMSを採用しているサイトに人気記事を表示します。
その際に、GA4のデータを使用してmicroCMSの記事データを定期的に更新し、そのデータを元に実装します。

Google Cloud Platformで鍵を取得する

サービスアカウントを作成する

  1. Google Cloud Platform(以下GCP)にログインし、「API とサービス」を開きます。
  2. 検索フォームにAnalyticsと入力するとGoogle Analytics Data APIがあるのでクリックします。
    Analyticsで検索した際のサジェスト画面
    一番下にある「Google Analytics Data API」を選択する
  3. 以下の画面で「有効にする」を押下します。
    Google Analytics Data APIのマーケットプレイスページ
    Google Analytics Data APIのマーケットプレイスページ
  4. メニューから[IAMと管理] > [サービスアカウント]を開き、「サービスアカウントを作成」を押下します。
    左のメニューの[IAMと管理]から[サービスアカウント]を開く
    左のメニューの[IAMと管理]から[サービスアカウント]を開く
    サービスアカウントを作成
    サービスアカウントを作成
  5. 任意のサービスアカウント名を入力してから「完了」を押下します。
    サービスアカウントの作成画面
    サービスアカウントの作成画面

鍵を取得する

  1. サービスアカウントの[操作]から[鍵を管理]を押下します。
    サービスアカウントの鍵の管理
    サービスアカウントの鍵の管理

  2. 鍵のページで[鍵を追加] > [新しい鍵をアップロード]を押下し、キーのタイプは[JSON]を選択し[作成]を押下します。
    [鍵を追加]>[新しい鍵を作成]を選択
    [鍵を追加]>[新しい鍵を作成]を選択

  3. [作成]を押下するとJSONファイルがダウンロードされます。
    秘密鍵の作成画面でJSONを選択
    秘密鍵の作成画面でJSONを選択
    秘密鍵は紛失すると再度作成できません。
    また、流出しないように厳重に管理する必要があります。

サービスアカウントにGA4の権限を付与する

  1. Google Analyticsを開き、左下の[管理]を押下します。
  2. [アカウントのアクセス管理]を開きます。
    [管理]>[アカウントのアクセス管理]を開く
    [管理]>[アカウントのアクセス管理]を開く
  3. 青丸の+ボタンを押下後、[ユーザーを追加]を押下します。
    アカウントのアクセス管理画面から[ユーザーを追加]を選択
    アカウントのアクセス管理画面から[ユーザーを追加]を選択
  4. 先ほど作成したサービスアカウントのメールアドレスを入力し、「新規ユーザーにメールで通知する」のチェックを外します。「標準の役割」は「閲覧者」にチェックを入れて「追加」を押下します。
    「役割とデータ制限の追加」の画面
    「役割とデータ制限の追加」の画面

GA4から取得したデータをmicroCMSに送信するAPIを作成する

  1. Google Analytics Data APIを使用してGoogle Analyticsにアクセスするために、Google Analytics Data: Node.js Clientをインストールします。
    ターミナル
    npm install @google-analytics/data
  2. GCPから取得したJSONファイルの中身からprivate_keyclient_emailをメモしておきます。
    credential.json
    {
      "type": "service_account",
      "project_id": "xxxxxxxxxx",
      "private_key_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "private_key": "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxx\n-----END PRIVATE KEY-----\n",
      "client_email": "xxxxxxxxxx@xxxxxxxxxx.iam.gserviceaccount.com",
      "client_id": "xxxxxxxxxx",
      "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/xxxxxxxxxx%40xxxxxxxxxx.iam.gserviceaccount.com",
      "universe_domain": "googleapis.com"
    }
  3. GA4の[管理] > [プロパティの詳細]の右上にあるプロパティIDをメモしておきます。
    [管理] > [プロパティの詳細]
    [管理] > [プロパティの詳細]
  4. 環境変数に以下のように設定します。
    .env.local
    GOOGLE_ANALYTICS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nxxxxxxxxxx\n-----END PRIVATE KEY-----\n"
    GOOGLE_ANALYTICS_CLIENT_EMAIL=xxxxxxxxxx@xxxxxxxxxx.iam.gserviceaccount.com
    GOOGLE_ANALYTICS_PROPERTY_ID=xxxxxxxxx
  5. src/libs/ga/getPageViews.ts を作成し、以下のように記述します。
    getPageViews.ts
    import { BetaAnalyticsDataClient } from '@google-analytics/data'
    
    const analyticsDataClient = new BetaAnalyticsDataClient({
      credentials: {
        client_email: process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL,
        private_key: process.env.GOOGLE_ANALYTICS_PRIVATE_KEY?.replace(/\\n/g, '\n'),
      },
    })
    
    export const getPageViews = async (
      startDate: '7daysAgo' | '14daysAgo' | '30daysAgo',
      limit: number = 10000,
    ) => {
      const [response] = await analyticsDataClient.runReport({
        property: `properties/${process.env.GOOGLE_ANALYTICS_PROPERTY_ID}`,
        dateRanges: [
          {
            startDate,
            endDate: 'today',
          },
        ],
        dimensions: [
          {
            name: 'pagePath',
          },
        ],
        metrics: [
          {
            name: 'screenPageViews',
          },
        ],
        dimensionFilter: {
          filter: {
            fieldName: 'pagePath',
            stringFilter: {
              matchType: 'BEGINS_WITH',
              value: '/posts/',
            },
          },
        },
        limit,
      })
    
      return response.rows?.map((row) => {
        const path =
          row.dimensionValues !== null && row.dimensionValues !== undefined
            ? row.dimensionValues[0].value
            : null
        const postId = path ? path.split('/').pop() : null // pathからpostIdを抽出
    
        return {
          path,
          postId,
          readCount:
            row.metricValues !== null && row.metricValues !== undefined
              ? Number(row.metricValues[0]?.value)
              : 0,
        }
      })
    }
  6. src/libs/microcms.ts に以下のように記述し、記事のデータを更新できるようにupdatePostDataを作成します。
    microcms.ts
    import { createClient } from "microcms-js-sdk";
    
    if (!process.env.MICROCMS_SERVICE_DOMAIN) {
      throw new Error("MICROCMS_SERVICE_DOMAIN is required");
    }
    
    if (!process.env.MICROCMS_API_KEY) {
      throw new Error("MICROCMS_API_KEY is required");
    }
    
    /**
     * MicroCMSのクライアントを初期化して生成します。
     * サービスドメインとAPIキーは環境変数から取得します。
     */
    export const client = createClient({
      serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN || "",
      apiKey: process.env.MICROCMS_API_KEY || "",
    });
    
    export const updatePostData = async (contentId: string, editData: any) => {
      try {
        await client.update({
          endpoint: "posts",
          contentId,
          content: {
            ...editData,
          },
        });
      } catch (error) {
        console.error(`${contentId}:`, error);
      }
    };
  7. src/app/api/ga/fetchPageViews/route.ts を作成し、microCMSにデータを送信するためのAPIを作成します。
    route.ts
    import { getPageViews } from '@/libs/ga/getPageViews'
    import { updatePostData } from '@/libs/microcms'
    import { NextRequest } from 'next/server'
    
    export async function GET(request: NextRequest) {
      try {
        const pageViewsPosts = await getPageViews('30daysAgo')
    
        if (!pageViewsPosts || pageViewsPosts.length === 0) {
          return new Response('Posts Not Found', { status: 404 })
        }
    
        const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
    
        for (const post of pageViewsPosts) {
          if (post.postId) {
            try {
              await updatePostData(post.postId, {
                screenPageViews: post.readCount || 0,
              })
              console.log(
                `${post.postId} の screenPageViews を ${post.readCount || 0} に更新しました。`,
              )
            } catch (error) {
              console.error(`${post.postId} の更新に失敗しました。`)
            }
            await delay(500)
          }
        }
    
        return new Response('Success', { status: 200 })
      } catch (error: any) {
        return new Response(error?.message, { status: 400 })
      }
    }
    microCMSのPATCHリクエストは1秒間に5回までしか送信できないため、delay関数を作成してリクエストの間隔を調整しています。
    VercelのCron JobsではGETリクエストしか送信できなかったため、GETリクエストにしています。
  8. PATCHリクエストを送信できるように、microCMSのAPIキーのデフォルト権限からPATCHにチェックを入れてください。
    microCMSのAPIのデフォルト権限設定
    microCMSのAPIのデフォルト権限設定

    Next.jsで作成したAPIはGETリクエストですが、microCMSのAPIはPATCHリクエストを送信しています。

ページに出力する

今回はコンポーネントとして使いまわせるようにしたいので、Client Componentsとして非同期でデータを取得します。

  1. src/libs/microcms.ts に以下の記述を追加します。
    microcms.ts
    /**
     * MicroCMSからすべての投稿を取得します。
     *
     * @param queries - MicroCMSのクエリオプション
     * @returns - 投稿のリスト
     */
    export const getAllPosts = async (queries?: MicroCMSQueries) => {
      const listData = await client.getList<any>({
        customRequestInit: {
          next: {
            revalidate: 60,
          },
        },
        endpoint: 'posts',
        queries,
      })
    
      return listData
    }
  2. src/app/components/PopularPosts.tsx を作成し、以下のように記述します。
    PopularPosts.tsx
    import { getAllPosts } from "@/libs/microcms";
    import Link from "next/link";
    
    export const PopularPostsList = async () => {
      const { contents } = await getAllPosts({
        orders: "-screenPageViews",
        limit: 3,
      });
    
      return (
        <ol>
          {contents.map((post) => (
            <li key={post.id}>
              <Link href={`/posts/${post.id}`}>{post.title}</Link>
            </li>
          ))}
        </ol>
      );
    };
    
  3. src/app/page.tsx でPopularPostsコンポーネントを呼び出します。この時、Suspenseでラップしてください。
    page.tsx
    import { PopularPostsWidget } from '@/components/PopularPostsWidget'
    import { Suspense } from 'react'
    
    export const  = () => {
      return (
        <div>
          <Suspense fallback={<p>Loading...</p>}>
            <PopularPosts />
          </Suspense>
        </div>
      )
    }
    

    これでページに人気記事のランキングを出せるようになりました。

参考・引用

この記事をシェアする