Next.js (TypeScript) で Google Analytics から人気記事を取得する方法をテスト駆動開発しながら解説

Next.js (TypeScript) で Google Analytics から人気記事を取得する方法をテスト駆動開発しながら解説

はじめに

どーも、入田 / ぐるたか @guru_taka です!
本記事では Next.js (TypeScript) において、Google Analytics のデータを用いて、人気記事を表示させる方法を紹介します。Google Analytics Reporting API v4 のセットアップや基本的な使い方については、以前まとめた Node.js (TypeScript) で Google Analytics Reporting API v4 を使用する方法を参考にしてみてください。
本記事では、人気記事のパス(ex: /tech/~~~)を配列で取得することをゴールとしており、Jest を用いたテスト駆動開発も行います。参考になれば幸いです!
▼ 人気記事のイメージ図
next-popular-1

バージョン情報

  • node:15.11.0
  • next: 11.0,
  • react: 17.0.2,
  • react-dom: 17.0.2
  • typescript:4.3.2
  • googleapis:76.0.0
  • jest: 26.0.21

結論:全体のコード

最初に全体のコードを共有いたします。
// src/lib/ga.ts import { GaxiosResponse } from 'gaxios'; import { google, analyticsreporting_v4 } from 'googleapis'; // JWT 認証 const client = new google.auth.JWT({ email: process.env.GA_CLIENT_EMAIL, key: process.env.GA_PRIVATE_KEY?.replace(/\\n/g, '\n'), scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); const analyticsreporting = google.analyticsreporting({ version: 'v4', auth: client, }); interface Args { startDate: '1daysAgo' | '7daysAgo'; filtersExpressions: Array<string>; pageSize: number; } export async function fetchPostGaData({ startDate, filtersExpressions, pageSize = 1000, }: Args): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const params = { requestBody: { reportRequests: [ { viewId: process.env.GA_VIEW_ID, dimensions: [ { name: 'ga:pagePath', }, ], metrics: [ { expression: 'ga:pageviews', }, ], orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], dateRanges: [ { startDate: startDate, endDate: '1daysAgo', }, ], pageSize: pageSize, dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: filtersExpressions, }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); } export async function fetchPopularPosts( pageSize: number, ): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const res = await fetchPostGaData({ startDate: '7daysAgo', // 期間は過去 1 週間 filtersExpressions: ['^/(blog|tech|news)/[0-9a-zA-Z\\-]+$'], // /blog/~~~, /tech/~~~, /news/~~~ のみ抽出 pageSize: pageSize, // 取得最大数 }); return res; } // 人気記事のパスを取得する関数 export async function getPopularPaths(pageSize = 1000): Promise<string[]> { const { reports } = (await fetchPopularPosts(pageSize)).data; // GA の行データを取得 const gaRowData = reports ? reports[0]?.data?.rows ?? [] : []; // パス取得 const popularPaths = gaRowData .filter((row) => row.dimensions && row.dimensions[0]) // dimensions や dimensions[0] が存在するデータのみ抽出 .map((row) => { return row.dimensions![0]; }); return popularPaths; }

人気記事を取得するまでのテスト駆動開発

fwywd では、テスト駆動開発を取り入れています。本記事では実践も意識し、以下 4 ステップのテストを行いながら、手順を解説します。
  1. API 疎通のテスト
  2. 鍵の情報を .env で管理した状態で API の疎通テスト
  3. 引数を指定した API の疎通テスト
  4. 人気記事のパスのみ抽出するテスト

1. API 疎通のテスト

まず、API 疎通のテストを行います。テスト通過の条件は、API の実行結果であるレスポンスの status200 であることです。条件を踏まえ、テストコードを記述すると、以下のようになります。
// __test__/tech/ga.test.tsx import { fetchPostGaData } from 'lib/ga'; describe('人気記事の取得', () => { test('Google Analytics の API 疎通を確認', async () => { const { status } = await fetchPostGaData(); expect(status).toBe(200); }); });
fetchPostGaData 関数は以下のように仮で定義します。
// src/lib/ga.tsx export async function fetchPostGaData() { const res = null; return await res; }
以下のようにテストを実行したとき、失敗すれば準備完了です。
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx ● Test suite failed to run __test__/tech/ga.test.tsx:4:11 - error TS2339: Property 'status' does not exist on type 'null'. 4 const { status } = await fetchPostGaData(); ~~~~~~ Test Suites: 1 failed, 1 total Tests: 0 total Snapshots: 0 total Time: 7.613 s, estimated 9 s Ran all test suites matching /ga.test.tsx/i.
次はテストが成功するように、fetchPostGaData 関数を実装します。全体のコードは以下の通りです。コメントで解説しています。
// src/lib/ga.tsx import { GaxiosResponse } from 'gaxios'; import { google, analyticsreporting_v4 } from 'googleapis'; // 鍵の情報 const key: { type: string; project_id: string; private_key_id: string; private_key: string; client_email: string; client_id: string; auth_uri: string; token_uri: string; auth_provider_x509_cert_url: string; client_x509_cert_url: string; } = require('./kye.json'); // JWT 認証 // client_email と private_key のみ必要 const client = new google.auth.JWT({ email: key.client_email, key: key.private_key, scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); const analyticsreporting = google.analyticsreporting({ version: 'v4', auth: client, }); export async function fetchPostGaData(): Promise< GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse> > { const params = { requestBody: { reportRequests: [ { // Google Analytics のビュー ID viewId: '~~~~~~~~', dimensions: [ { name: 'ga:pagePath', }, ], // 取得したい metrics // 今回は PV 数のみ取得 metrics: [ { expression: 'ga:pageviews', }, ], // 並び順 // 今回は PV を基準に、降順で取得 orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], // 期間 dateRanges: [ { startDate: '1daysAgo', endDate: '1daysAgo', }, ], // ページの最大取得数 pageSize: 3, // パスのフィルタリング dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: ['/test'], }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); }
Google Analytics Reporting API v4 における必要なパラメータや JWT 認証などの基本的な使い方の詳細は、Node.js (TypeScript) で Google Analytics Reporting API v4 を使用する方法をご覧ください。
以下のようにテストを実行し、エラーが出力しなければ成功です。
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx (8.502 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (637 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 8.864 s, estimated 9 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 12.17s.

2. 鍵の情報を .env で管理した状態で API の疎通テスト

現在、private_keyclient_key、Google Analytics のビュー ID を環境変数で管理して、API 疎通のテストを行います。
まず .env に環境変数を管理しましょう。環境変数については、以下の記事をご覧ください。
参考:Next.js における環境変数 (env) の基本的な設定方法 | fwywd(フュード)
// .env GA_CLIENT_EMAIL='~~~~~~~' GA_PRIVATE_KEY='~~~~~~~' GA_VIEW_ID='~~~~~~~'
続いて、それぞれの情報を環境変数に差し替えます。コードは以下の通りです。
// src/lib/ga.tsx import { GaxiosResponse } from 'gaxios'; import { google, analyticsreporting_v4 } from 'googleapis'; // JWT 認証 // client_email と private_key のみ必要 // .env で管理 const client = new google.auth.JWT({ email: process.env.GA_CLIENT_EMAIL, key: process.env.GA_PRIVATE_KEY, scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); const analyticsreporting = google.analyticsreporting({ version: 'v4', auth: client, }); export async function fetchPostGaData(): Promise< GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse> > { const params = { requestBody: { reportRequests: [ { // Google Analytics のビュー ID // .env で管理 viewId: process.env.GA_VIEW_ID, dimensions: [ { name: 'ga:pagePath', }, ], // 取得したい metrics // 今回は PV 数のみ取得 metrics: [ { expression: 'ga:pageviews', }, ], // 並び順 // 今回は PV を基準に、降順で取得 orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], // 期間 dateRanges: [ { startDate: '1daysAgo', endDate: '1daysAgo', }, ], // ページの最大取得数 pageSize: 3, // パスのフィルタリング dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: ['/test'], }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); }
テストを実行すると、以下のようなエラーが出力されます。
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx (9.02 s) 人気記事の取得 ✕ Google Analytics の API 疎通を確認 (6 ms) ● 人気記事の取得 › Google Analytics の API 疎通を確認 No key or keyFile set. 65 | }, 66 | }; > 67 | return await analyticsreporting.reports.batchGet(params); | ^ 68 | } 69 | 70 | // export async function fetchPopularPosts(): Promise<
原因は Jest が環境変数を読み込めていないためです。対応策は以下の通りです。
テストを始める前に @next/envloadEnvConfig() で環境変数ファイルを読み込む作戦です。
これで.env や.env.test.local などで定義した環境変数をテスト環境でも利用できます。
loadEnvConfig()process.env.PWD だけでも動きますが、Windows 環境では process.env.PWDundefined を返す可能性があるため、process.cwd() も追加しています。
引用:環境変数を含む Next.js のコードを Jest でテストする - Breath Note
上記の記事を参考に、setupEnv.ts ファイルを作成し、jest.config.js を設定します。コードは以下の通りです。
// src/lib/test/setupEnv.ts import { loadEnvConfig } from '@next/env'; export default async (): Promise<void> => { loadEnvConfig(process.env.PWD || ''); };
// jest.config.js module.exports = { ... globalSetup: '<rootDir>/src/lib/test/setupEnv.ts', ... }
上記の方法では .env を読み込むことができますが、.env.local 等には対応していないため、注意しましょう
テストを実行すると、今度は異なるエラーが出力されます。原因は process.env.GA_PRIVATE_KEY です。process.env.GA_PRIVATE_KEY の改行コードを元に戻さなくてはいけません
以下のように process.env.GA_PRIVATE_KEY の改行コードをリプレイスしましょう。
// src/lib/ga.tsx ... // JWT 認証 // client_email と private_key のみ必要 // .env で管理 const client = new google.auth.JWT({ email: process.env.GA_CLIENT_EMAIL, // 改行コードをリプレイス key: process.env.GA_PRIVATE_KEY?.replace(/\\n/g, '\n'), scopes: ['https://www.googleapis.com/auth/analytics.readonly'], }); ...
テストを実行し、エラーが出ることなく通過すれば成功です!
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx (9.385 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (1241 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 9.843 s, estimated 10 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 12.16s.
また、private_keyclient_key を環境変数で管理できたため、JSON 形式の鍵は削除して構いません!

3. 引数を指定した API の疎通テスト

続いて、引数を渡して API の疎通テストを行います。汎用性も考慮し、引数は以下 3 つとします。
  • startDate
  • filtersExpressions
  • pageSize
    • デフォルト値として 1000 を設定
まずは、以下のようにテストコードを記述しましょう。
// __test__/tech/ga.test.tsx describe('人気記事の取得', () => { test('Google Analytics の API 疎通を確認', async () => { const { status } = await fetchPostGaData({ startDate: '7daysAgo', filtersExpressions: ['/test'], pageSize: 3, }); expect(status).toBe(200); }); });
テストを実行し、エラーが出力すれば準備完了です。
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx ● Test suite failed to run __test__/tech/ga.test.tsx:5:46 - error TS2554: Expected 0 arguments, but got 1. 5 const { status } = await fetchPostGaData({ ~ 6 startDate: '7daysAgo', ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... 8 pageSize: 3, ~~~~~~~~~~~~~~~~~~ 9 }); ~~~~~
テストコードに記述した引数を、fetchPostGaData 関数が受け取れるようにコードを変更しましょう。
// src/lib/ga.tsx ... // 引数の型定義 interface Args { startDate: '1daysAgo' | '7daysAgo'; filtersExpressions: Array<string>; pageSize: number; } // 引数をセット export async function fetchPostGaData({ startDate, filtersExpressions, pageSize, }: Args): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const params = { requestBody: { reportRequests: [ { viewId: process.env.GA_VIEW_ID, dimensions: [ { name: 'ga:pagePath', }, ], metrics: [ { expression: 'ga:pageviews', }, ], orderBys: [ { fieldName: 'ga:pageviews', sortOrder: 'DESCENDING', }, ], dateRanges: [ { startDate: startDate, endDate: '1daysAgo', }, ], pageSize: pageSize, dimensionFilterClauses: [ { filters: [ { dimensionName: 'ga:pagePath', operator: 'REGEXP', expressions: filtersExpressions, }, ], }, ], }, ], }, }; return await analyticsreporting.reports.batchGet(params); } ...
テストが通れば成功です!
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (1186 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 3.198 s, estimated 9 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 5.47s.

4. 人気記事のパスのみ抽出するテスト

最後に、人気記事のパスのみ抽出するテストを行います。本記事では、以下 3 つの記事から人気記事を抽出できているか、確認するテストを行います。
  • ブログ記事:/blog/*
  • 技術記事:/tech/*
  • ニュース記事:/news/*
テストコードは以下の通りです。
// __test__/tech/ga.test.tsx import { fetchPostGaData, getPopularPaths } from 'lib/ga'; describe('人気記事の取得', () => { ... // 追記 test('ブログ記事、技術記事、ニュース記事の中から人気記事を抽出', async () => { const popularPaths = await getPopularPaths(); // ブログ記事が少なくとも 1 記事含む expect(popularPaths.some((path) => path.startsWith('/blog/'))).toBeTruthy(); // 技術記事が少なくとも 1 記事含む expect(popularPaths.some((path) => path.startsWith('/tech/'))).toBeTruthy(); // ニュース記事が少なくとも 1 記事含む expect(popularPaths.some((path) => path.startsWith('/news/'))).toBeTruthy(); // ブログ記事、技術記事、ニュース記事以外が存在しない expect( popularPaths.some( (path) => path.startsWith('/blog/') || path.startsWith('/tech/') || path.startsWith('/news/'), ), ).toBeTruthy(); }); });
// src/lib/ga.tsx ... export async function getPopularPaths(): Promise<string[]> { const popularPosts: string[] = []; return popularPosts; } ...
テストを実行し、エラーが出力されれば準備完了です。
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx (9.545 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (683 ms) ✕ ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 (2 ms) ● 人気記事の取得 › ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 expect(received).toBeTruthy() Received: false 16 | 17 | // ブログ記事が少なくとも 1 記事含む > 18 | expect(popularPaths.some((path) => path.startsWith('/blog/'))).toBeTruthy();
テストコードが通るように、getPopularPaths 関数を実装します。まず、ブログ記事のみ抽出してみましょう。
コードは以下の通りです。コメントで解説しています。
// src/lib/ga.tsx ... export async function fetchPopularPosts( pageSize: number, ): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const res = await fetchPostGaData({ startDate: '7daysAgo',// 期間は過去 1 週間 filtersExpressions: ['^/(blog)/[0-9a-zA-Z\\-]+$'],// /blog/~~~のみ、正規表現で抽出 pageSize: pageSize,// 取得最大数 }); return res; } export async function getPopularPaths(pageSize = 1000): Promise<string[]> { const { reports } = (await fetchPopularPosts(pageSize)).data; // GA の行データを取得 const gaRowData = reports ? reports[0]?.data?.rows ?? [] : []; // パス取得 const popularPaths = gaRowData .filter((row) => row.dimensions && row.dimensions[0]) // dimensions や dimensions[0] が存在するデータのみ抽出 .map((row) => { return row.dimensions![0]; }); return popularPaths; } ...
テストを実行すると、以下のようにブログ記事の存在テストは通過したものの、技術記事が存在しないため、エラーが出力されます
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 FAIL __test__/tech/ga.test.tsx (9.81 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (579 ms) ✕ ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 (631 ms) ● 人気記事の取得 › ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 expect(received).toBeTruthy() Received: false 18 | expect(popularPaths.some((path) => path.startsWith('/blog/'))).toBeTruthy(); 19 | // 技術記事が少なくとも 1 記事含む > 20 | expect(popularPaths.some((path) => path.startsWith('/tech/'))).toBeTruthy();
以下のようにコードを変更し、技術記事とニュース記事も人気記事の対象にしましょう。
// src/lib/ga.tsx ... export async function fetchPopularPosts( pageSize: number, ): Promise<GaxiosResponse<analyticsreporting_v4.Schema$GetReportsResponse>> { const res = await fetchPostGaData({ startDate: '7daysAgo',// 期間は過去 1 週間 // |tech|news を追記 filtersExpressions: ['^/(blog|tech|news)/[0-9a-zA-Z\\-]+$'], pageSize: pageSize,// 取得最大数 }); return res; } ...
テストを実行し、エラーなく通れば成功です!
# npm の場合 npm test ga.test.tsx # yarn の場合 yarn test ga.test.tsx # 実行結果 PASS __test__/tech/ga.test.tsx (9.585 s) 人気記事の取得 ✓ Google Analytics の API 疎通を確認 (600 ms) ✓ ブログ記事、技術記事、ニュース記事の中から人気記事を抽出 (723 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 10.019 s Ran all test suites matching /ga.test.tsx/i. ✨ Done in 12.46s.

最後に

以上になります。今回はテスト駆動開発をしながら、Next.js (TypeScript) で Google Analytics から人気記事を取得する方法を解説しました!
実践的な Google Analytics Reporting API v4 の活用方法やテスト駆動開発におけるイメージの参考になれば嬉しいです。
ここまでご覧いただき、ありがとうございました!

株式会社キカガク コンテンツマーケティング責任者
入田 / ぐるたか
twitter: @guru_taka

参考文献