静的サイトで人気記事ランキングを自動更新する|Cloudflare Web AnalyticsのAPI×GitHub Actions
WordPress 時代は、プラグインで記事ごとの閲覧数を数えて「人気記事」を表示していました。
ところが静的サイトに移行すると、記事は HTML ファイルとして配信されるだけなので、閲覧数を数えて保存する場所がありません。
そこでこのブログでは、Cloudflare の無料のアクセス解析(Web Analytics)のデータを使って、毎朝ランキングを作り直すようにしました。
仕組み
Web Analytics でページビューを計測する
すべてのページに Cloudflare Web Analytics の計測タグを入れておきます。Cookie を使わない、無料のアクセス解析です。
毎朝、直近 30 日のページビューを取得する
GitHub Actions で毎朝 5 時にスクリプトを動かし、API で記事ごとのページビューを取得します。
ランキングを作ってサイトを公開し直す
ページビューの多い順に並べた JSON ファイルを書き出し、サイトをビルドして公開します。
ランキングは 1 日 1 回しか変わりませんが、「人気記事」の用途ならこれで十分です。
必要なもの
| 項目 | 内容 |
|---|---|
| API トークン | 「Account Analytics: Read」の権限を付けて作成 |
| アカウント ID | Cloudflare のダッシュボードに表示されている ID |
| サイトタグ | Web Analytics のサイトごとの ID |
このブログでは、デプロイ用に作った API トークン(「Edit Cloudflare Workers」テンプレート)に「Account Analytics: Read」の権限を追加して、1 つのトークンで両方に使っています。
ページビューを取得する GraphQL
Web Analytics のデータは、Cloudflare の GraphQL API の rumPageloadEventsAdaptiveGroups から取得できます。ページのパス(requestPath)ごとにページビューを集計し、多い順に並べます。
const query = `query ($accountTag: string!, $filter: AccountRumPageloadEventsAdaptiveGroupsFilter_InputObject!) {
viewer {
accounts(filter: { accountTag: $accountTag }) {
rumPageloadEventsAdaptiveGroups(limit: 200, filter: $filter, orderBy: [count_DESC]) {
count
dimensions { requestPath }
}
}
}
}`;
const res = await fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST',
headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
body: JSON.stringify({
query,
variables: {
accountTag,
filter: { siteTag, bot: 0, datetime_geq: since.toISOString(), datetime_leq: until.toISOString() },
},
}),
});bot: 0 を指定して、ボットによるアクセスを除いています。
サイトタグを自動で探す
サイトタグは、Web Analytics のサイト一覧の API から、計測タグのトークンが一致するものを探して取得しています。こうしておくと、設定ファイルには計測タグのトークンを 1 か所書くだけで済みます。
const res = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountTag}/rum/site_info/list?per_page=100`, {
headers: { authorization: `Bearer ${token}` },
});
const body = await res.json();
const found = body.result.find((s) => s.site_token === beacon);
return found.site_tag;ランキングを作る
取得したパスのうち、記事ページ(/entry/スラッグ/)だけを取り出して、ページビューの多い順に 30 件を JSON ファイルに書き出します。
日本語のスラッグの記事は、パスが %E3%81%... のようにエンコードされて届くので、デコードしてから記事のフォルダ名と照らし合わせています。カテゴリページ(/entry/category/...)や、もう存在しない記事は除外します。
for (const row of rows) {
let p = row.dimensions.requestPath;
try { p = decodeURIComponent(p); } catch {}
const slug = p.normalize('NFC').match(/^\/entry\/([^/]+)\/?$/)?.[1];
if (!slug || slug === 'category' || !existsSync(`src/content/blog/${slug}/index.md`)) continue;
views.set(slug, (views.get(slug) ?? 0) + row.count);
}サイト側では、この JSON の順番で人気記事を表示しています。移行直後でデータが少ないあいだは、WordPress のプラグインに残っていた累計の閲覧数で足りない分を補うようにしました。
GitHub Actions で毎朝更新する
記事を公開するときと同じワークフローに、定時実行(cron)を追加しています。
on:
push:
branches: [main]
schedule:
- cron: '0 20 * * *' # 毎日 5:00 JST
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# (チェックアウトと npm ci は省略)
- name: 人気記事ランキングを更新
# 集計に失敗しても前回のランキングのまま公開を続ける
continue-on-error: true
run: npm run update:popular
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: ビルド
run: npm run build
- name: デプロイ
run: npx wrangler deployGitHub Actions の cron は UTC で指定するので、日本時間の朝 5 時は 0 20 * * *(前日の 20 時 UTC)になります。
よくある質問
Cloudflare Web Analytics は無料ですか?
はい、無料で使えます。Cookie を使わないため、Cookie の同意バナーを出さなくても使いやすいアクセス解析です。
毎日ビルドし直すと、Cloudflare の料金はかかりますか?
Workers の静的アセットの配信は無料で、デプロイの回数にも料金はかかりません。GitHub Actions も、非公開リポジトリの無料枠の範囲で十分に収まります。
Google アナリティクスのデータでも同じことはできますか?
Google アナリティクスにもデータを取得する API があるので、同じ仕組みは作れます。ただ、認証の設定が Cloudflare より手間がかかるため、このブログでは Cloudflare Web Analytics を使いました。
まとめ
- 静的サイトでも、アクセス解析の API を使えば人気記事ランキングを作れる
- Cloudflare Web Analytics の GraphQL API で、記事ごとのページビューを取得できる
- GitHub Actions の定時実行で、毎朝自動でランキングを更新して公開できる
お問い合わせフォームも、同じく Cloudflare の無料の機能だけで作っています。
関連記事静的サイトにお問い合わせフォームを無料で付ける|Cloudflare Workers×Email Routing×Turnstile