Google Analytics Data APIを使用して、ブログの視聴回数ランキングを表示しよう!
背景と目的
みなさんこんにちは。クラウドエースの清野です。
弊社では、10月の組織改編により、事業領域ごとにチームが再編されました。
そのため、これまでバックエンドを担当していたメンバーも、フロントエンドやインフラ領域に携わる機会が増えています。
そこで、元々バックエンドエンジニアリング部に所属していた私が、フロント領域のNext.jsと新たに加入したメディア事業部で活かせそうな Google Analytics を使用した学習内容を共有したいと思います。
本記事のゴール
Google Analytics で集計したブログデータを Google Analytics Data API を用いて取得し、Next.js で構築した Web 画面に表示します。
使用技術の一覧
- Next.js
- Google Analytics
- Google Analytics Data API
- microCMS
- Vercel
手順
Google Analyticsの初期設定
https://www.google.com/analytics にアクセスし、初期設定を行います。
Next.jsへのGoogle Analytics組み込み
以下の公式ドキュメントを参考に、Next.js に Google Analytics を組み込みます。
// app/layout.js
import { GoogleAnalytics } from '@next/third-parties/google'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
<GoogleAnalytics gaId="G-XYZ" />
</html>
)
}
今回は、microCMS からブログ情報を取得します。
Next.js と microCMS の構成は、公式のテンプレートがございますのでそちらを参考にしてみてください。
microCMSのテンプレート
Google Cloud プロジェクトを作成し、Google Analytics Data API を有効化
Google Cloud プロジェクトを作成し、Google Analytics Data API を有効化します。
サービスアカウントの作成
- IAMと管理 > サービスアカウント > サービスアカウントを作成 を選択
- 任意のサービスアカウント名を入力し、完了 を選択
- 作成したサービスアカウントの詳細を確認し、メールアドレスを保管
Google Analyticsの設定
-
Google Analytics の ホーム画面 > 設定 > プロパティ > プロパティのアクセス管理 > ユーザーを追加 を選択
-
プロパティのアクセス管理でユーザーを新規追加
- ユーザーは、先ほど作成したサービスアカウントのメールアドレスとなります。
-
権限は、閲覧者以上の権限を付与してください。
Next.jsの実装
Google Analytics のどの指標を取得したいかはこちらの公式ドキュメントに記載があります。
今回は、ブログの視聴回数のランキングを取得したいので、screenPageViews
(視聴回数)を取得します。
また、dimensionFilter
を使用してブログとは関係ない記事を弾くようにしています(トップページやaboutページなど)。
const propertyId = {'プロパティID'};
const serviceAccountKey = JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS || '{}');
async function getAccessToken() {
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: await createJwt(),
}),
});
const data = await response.json();
return data.access_token;
}
async function createJwt() {
const header = {
alg: 'RS256',
typ: 'JWT',
};
const payload = {
iss: serviceAccountKey.client_email,
scope: 'https://www.googleapis.com/auth/analytics.readonly',
aud: 'https://oauth2.googleapis.com/token',
exp: Math.floor(Date.now() / 1000) + 3600,
iat: Math.floor(Date.now() / 1000),
};
const encodedHeader = btoa(JSON.stringify(header));
const encodedPayload = btoa(JSON.stringify(payload));
const keyData = str2ab(atob(serviceAccountKey.private_key.replace(/-----BEGIN PRIVATE KEY-----|-----END PRIVATE KEY-----|\n/g, '')));
const key = await crypto.subtle.importKey(
'pkcs8',
keyData,
{
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-256',
},
false,
['sign']
);
const signature = await crypto.subtle.sign(
'RSASSA-PKCS1-v1_5',
key,
new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`)
);
return `${encodedHeader}.${encodedPayload}.${btoa(String.fromCharCode(...new Uint8Array(signature)))}`;
}
function str2ab(str: string): ArrayBuffer {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
export async function runReportByPageViews() {
try {
const accessToken = await getAccessToken();
const response = await fetch(`https://analyticsdata.googleapis.com/v1beta/properties/${propertyId}:runReport`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
dateRanges: [
{
startDate: '2024-11-11',
endDate: 'today',
},
],
dimensions: [
{
name: 'pagePath',
},
],
metrics: [
{
name: 'screenPageViews',
},
],
orderBys: [
{
desc: true,
metric: { metricName: 'screenPageViews' },
},
],
dimensionFilter: {
notExpression: {
filter: {
fieldName: 'pagePath',
inListFilter: {
values: ['/blog', '/', '/about', '/contact'],
},
},
},
},
limit: 5,
}),
});
const reportData = await response.json();
if (reportData.rows) {
return reportData.rows.map((row: { dimensionValues: { value: string }[]; metricValues: { value: string }[] }) => ({
path: row.dimensionValues?.[0]?.value || '',
screenPageViews: row.metricValues?.[0]?.value || '',
}));
} else {
return [];
}
} catch (error) {
console.error('Error running report by page views:', error);
throw new Error('Failed to fetch report data by page views');
}
}
次に Next.js のAPIルートを定義するファイル/api/report/route.ts
を作成します。
microCMS のすべての記事を取得してきています。
import { NextResponse } from 'next/server';
import { runReportByPageViews } from '@/app/libs/runReport';
import { getAllPosts } from '@/app/libs/client';
export async function GET() {
try {
const reportDataByPageViews = await runReportByPageViews();
const blogPosts = await getAllPosts();
return NextResponse.json({ reportDataByPageViews, blogPosts });
} catch (error) {
console.error('Error fetching report data:', error);
return NextResponse.json({ error: 'Failed to fetch report data' }, { status: 500 });
}
}
最後にトップページで出力します。
先ほど作成したapi/report
を呼び出して画面で表示して完成です。
"use client";
import { useEffect, useState } from 'react';
import styles from './page.module.css';
interface ReportDataByPageViews {
path: string;
screenPageViews: string;
title?: string;
}
interface BlogPost {
id: string;
title: string;
}
export const runtime = 'edge';
export default function Home() {
const [reportDataByPageViews, setReportDataByPageViews] = useState<ReportDataByPageViews[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const items = document.querySelectorAll(`.${styles.mvv_item}`);
items.forEach((item, index) => {
setTimeout(() => {
item.classList.add(styles.visible);
}, index * 700);
});
async function fetchData() {
try {
const response = await fetch('/api/report');
const data = await response.json();
if (data.reportDataByPageViews && Array.isArray(data.reportDataByPageViews)) {
const enrichedData = data.reportDataByPageViews.map((row: ReportDataByPageViews) => {
const matchingPost = data.blogPosts.find((post: BlogPost) => row.path.includes(post.id));
return {
...row,
title: matchingPost ? matchingPost.title : 'No title'
};
});
setReportDataByPageViews(enrichedData);
} else {
setError('Invalid data format for page views report');
}
} catch {
setError('Failed to fetch report data');
}
}
fetchData();
}, []);
return (
<>
<div className={styles.hero}></div>
<section className={styles.reportSection}>
<h2 className={styles.reportTitle}>人気のBLOGページ</h2>
{error ? (
<p className={styles.error}>{error}</p>
) : (
<ul className={styles.reportList}>
{reportDataByPageViews
.filter(row => row.title !== 'No title')
.map((row, index) => (
<li key={index} className={styles.reportItem}>
<a href={row.path}>{row.title}</a> - {row.screenPageViews} views
</li>
))}
</ul>
)}
</section>
</>
);
}
※今回のランクキングデータは、テスト用データを使用しています。
Vercelへのデプロイ
以下のURLから Vercel にデプロイし、完成となります。
最後に
今回の記事では、Google Analytics Data API を活用してブログの視聴回数ランキングを表示する方法をご紹介しました。
Google Analytics は他にも様々な機能がありますので、ぜひ試してみてください。
Discussion