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

参考文献

RSS フィードで fwywd の技術記事を購読しよう

fwywd(フュード) のシェアボタンにある RSS フィードを利用すれば、新しい記事が出るたびに自動的に通知を無料で受け取ることができます。
Slack への連携はたったの5分で行うことができ、以下の記事でその手順を具体的に紹介しています。
ぜひ、fwywd(フュード) が日々更新する技術記事をキャッチアップしていただけると嬉しいです。
subscribe-rss-feed

fwywd では開発メンバーを募集しています

recruitment
fwywd では採用試験を無料で公開しています。
採用への応募の有無を問わず、Web アプリケーションの開発を学びたい多くの方にとって有益な試験内容となるように設計しています。 ぜひ、fwywd の面白い採用試験を覗いてみてください。