Next.jsで人気記事の取得と表示

updated Sep 27, 2021created Sep 27, 2021

設計

Google Analytics Reporting API からPV数の多い記事を取得し、build時にjsonに出力します。next build時に、jsonを読み取り静的出力します。

実装

ga.js
const fs = require('fs'); require('dotenv').config(); const { google } = require('googleapis'); 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'], }); console.log('✅ Client作成'); const analyticsreporting = google.analyticsreporting({ version: 'v4', auth: client, }); async function getPopularPostData() { const res = await analyticsreporting.reports.batchGet({ requestBody: { reportRequests: [ { viewId: process.env.GA_VIEW_ID, // 👈 Analytics ViewID の指定 pageSize: 50, // 👈 要求するページの数 dateRanges: [ { startDate: '30daysAgo', endDate: 'today', }, ], metrics: [{ expression: 'ga:pageviews' }], dimensions: [{ name: 'ga:pagePath' }, { name: 'ga:pageTitle' }], orderBys: [{ fieldName: 'ga:pageviews', sortOrder: 'DESCENDING' }], }, ], }, }); return res.data; } getPopularPostData().then((data) => { console.log('✅ 人気記事の取得'); const { reports } = data; 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]; }); console.log('popularPaths:', popularPaths); fs.writeFile('ga.json', JSON.stringify(popularPaths), (err) => { console.log('🎉 ga.json生成'); }); });

ga.jsonが生成される

Vercelビルド時に実行

pacakge.json
"scripts": { "build": "node ga.js && next build && next-sitemap" }

表示

index.jsx
export const getStaticProps: GetStaticProps<Props> = async () => { // 👇 `ga.json`から人気の記事を取得する let popularArticles = await Promise.all( popularPaths.map(async (path: string) => { let remakePath = path.replace('/', ''); // 👈 先頭の`/`を削除する console.log('✅ PATH:', path.replace('/', '')); if (remakePath) { let article = await client.getEntries({ content_type: CONTENT_TYPE.ARTICLE, 'fields.slug': remakePath, }); if (article?.total === 1) { return article.items[0]; } } }) ); popularArticles = popularArticles.filter(Boolean); // 👈 Falseな要素を削除 if (popularArticles.length > 10) popularArticles.length = 10; // 👈 11個目の要素から全て削除 ... };