Kunden bitten uns häufig, die Platzierung ihrer Artikel in den Suchergebnissen anzuzeigen.
Wenn Artikelrankings von micorCMS auf einer Jamstack-Website angezeigt werden sollen, ist es üblich, eine eigene API zu erstellen.
Sollten Sie es jedoch implementieren wollen, aber keine API mehr verfügbar sein,Schreiben Sie Seitenaufrufe direkt in den bestehenden Artikelinhalt.Es gibt auch diesen Ansatz.
Dieses Mal stellen wir Ihnen ein Beispiel für diesen Ansatz an einer Website vor, die auf SSG mit microCMS + Cloudflare Pages veröffentlicht wurde!
Cloudflare ist der breiten Öffentlichkeit jedoch auf eine ungewöhnliche Weise bekannt geworden, dabei testen wir diese Technologie bereits seit einiger Zeit auf unserer Website – auf Wunsch unserer Kunden!https://www.liberogic.jp/topics/
Wann ist diese Vorgehensweise geeignet?
- Wenn Sie eine Top-10-Rangliste in der Seitenleiste oder Fußzeile anzeigen möchten
- Wenn Echtzeitleistung nicht erforderlich ist
- Für das microCMS-API-Kontingent ist kein Platz.
- Wenn Sie eine einfache Implementierung mit statischer Seitengenerierung (SSG) wünschen
Schritt 1: Bereiten Sie Ihr microCMS vor (Schema-Erweiterung und Webhook-Steuerung)
1-1. Erweiterung des API-Schemas
Fügen Sie dem bestehenden Artikel-Endpunkt zwei Ranking-Felder hinzu.
pageView(Zahl): Speichert die kumulierten Seitenaufrufe der letzten 30 Tage, ermittelt über GA4.lastUpdatedPV(Datum und Uhrzeit): Speichert Datum und Uhrzeit der letzten Aktualisierung des PV.
1-2. Festlegen der API-Schlüsselberechtigungen
In den Einstellungen für den zu verwendenden API-SchlüsselPATCHKreuzen Sie das Kästchen an.
1-3. Webhook-Einstellungen (eine Umgehungslösung für kontinuierliche Builds)
Um zu verhindern, dass beim Aktualisieren der Seitenaufrufe für jeden Artikel mehrere Builds auftreten, fügen Sie dem Webhook-Trigger Folgendes hinzu:Veröffentlichung von Inhalten (API-Operationen)"Häkchen entfernen."
Schritt 2: Konfigurieren Sie die Authentifizierung in der Google Cloud Console.
Um von Cloudflare Workers aus zuzugreifen,ServicekontoUndDaten-APIBereiten Sie Folgendes vor.
2-1. API aktivieren
In Ihrem Google Cloud Console-Projekt,Google Analytics Data APIAktivieren
2-2. Erstellen eines Dienstkontos und eines Schlüssels
Erstellen Sie ein Servicekonto für die GA4-Integration und kopieren Sie die E-Mail-Adresse.
Klicken Sie im Menüpunkt „Schlüssel“ auf „Schlüssel hinzufügen“ und dann auf „Neuen Schlüssel erstellen“. Erstellen und laden Sie anschließend einen Schlüssel vom Typ JSON herunter.
2-3. GA4-Berechtigungserteilung
Fügen Sie im Abschnitt „Zugriffsverwaltung“ Ihrer GA4-Property die unter 2-2 genannte E-Mail-Adresse als Benutzer hinzu und erteilen Sie ihr mindestens die Berechtigung „Betrachter“.
Schritt 3: Cloudflare Workers erstellen und konfigurieren (GA4-Datenerfassung)
3-1. Erstellen von Mitarbeitern und Festlegen von Umgebungsvariablen
Arbeiterga4-ranking-updaterErstellen Sie eine neue mit einem Namen wie
Legen Sie die Umgebungsvariablen in den Einstellungen fest. Registrieren Sie den Schlüssel als Geheimnis.
|
MICROCMS_API_KEY |
microCMS API-Schlüssel |
|---|---|
|
MICROCMS_API_URL |
https://[ID].microcms.io/api/v1 |
|
GA4_PROPERTY_ID |
GA4-Objekt-ID |
|
GA4_SERVICE_ACCOUNT_CREDENTIALS |
Der gesamte Inhalt der JSON-Schlüsseldatei |
|
GA4_PRIVATE_KEY_BASE64 |
Aus dem JSON-Wert private_key |
3-2. GA4 Datenaktualisierungs-Worker
Klicken Sie auf „Code bearbeiten“ und ändern Sie den Inhalt von worker.js wie folgt:
const MICROCMS_ENDPOINT_NAME = '[記事のエンドポイント名]';
let contentIdToSlugMap = {};
// ----------------------------------------------------------------------
// 1. microCMSから全記事のIDとスラッグを取得し、マップを作成する
// ----------------------------------------------------------------------
async function fetchContentMap(env) {
const apiEndpoint = `${env.MICROCMS_API_URL}/${MICROCMS_ENDPOINT_NAME}`;
const slugToIdMap = {};
let offset = 0;
const limit = 100;
while (true) {
const url = `${apiEndpoint}?fields=id,slug&limit=${limit}&offset=${offset}`;
const response = await fetch(url, {
headers: { 'X-MICROCMS-API-KEY': env.MICROCMS_API_KEY },
});
if (!response.ok) {
throw new Error(`microCMS Map Fetch Error: ${response.status} ${await response.text()}`);
}
const data = await response.json();
data.contents.forEach(item => {
if (item.slug && item.id) {
slugToIdMap[item.slug] = item.id;
}
});
if (data.contents.length < limit || data.totalCount <= (offset + limit)) {
break;
}
offset += limit;
}
contentIdToSlugMap = slugToIdMap;
console.log(`microCMSから合計 ${Object.keys(slugToIdMap).length} 件の記事IDマップを取得しました。`);
}
// ----------------------------------------------------------------------
// 2. JWT 認証ヘルパー関数群 (GA4 アクセストークン取得用)
// ----------------------------------------------------------------------
// Base64Url エンコード/デコード
const base64UrlEncode = (data) => btoa(String.fromCharCode(...new Uint8Array(data))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');
const base64UrlDecode = (data) => Uint8Array.from(atob(data.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
async function importPrivateKey(keyBase64) {
// 秘密鍵本体をデコードし、DER形式のバイナリにする
const binaryDer = base64UrlDecode(keyBase64);
return crypto.subtle.importKey(
'pkcs8',
binaryDer,
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
false,
['sign']
);
}
// GA4 Service Accountの認証情報からAccess Tokenを取得するメイン関数
async function getAccessToken(credentialsString, privateKeyBodyBase64) {
// 認証情報JSONをパースし、client_emailを取得
const creds = JSON.parse(credentialsString);
const serviceAccountEmail = creds.client_email;
const now = Math.floor(Date.now() / 1000);
const expiry = now + 3600; // 有効期限: 1時間後
const header = { alg: 'RS256', typ: 'JWT' };
const payload = {
iss: serviceAccountEmail,
scope: '<https://www.googleapis.com/auth/analytics.readonly>',
aud: '<https://oauth2.googleapis.com/token>',
exp: expiry,
iat: now,
}
// 2. 署名するデータ(Header.Payload)を作成
const encodedHeader = base64UrlEncode(new TextEncoder().encode(JSON.stringify(header)));
const encodedPayload = base64UrlEncode(new TextEncoder().encode(JSON.stringify(payload)));
const signatureInput = `${encodedHeader}.${encodedPayload}`;
// 3. 秘密鍵をインポートし、JWTに署名
const key = await importPrivateKey(privateKeyBodyBase64);
const signature = await crypto.subtle.sign(
{ name: 'RSASSA-PKCS1-v1_5' },
key,
new TextEncoder().encode(signatureInput)
);
const encodedSignature = base64UrlEncode(new Uint8Array(signature));
// 4. JWTを完成させる
const jwt = `${signatureInput}.${encodedSignature}`;
// 5. Googleトークンエンドポイントへリクエスト
const tokenResponse = 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: jwt,
}),
});
if (!tokenResponse.ok) {
throw new Error(`Token request failed: ${tokenResponse.status} ${await tokenResponse.text()}`);
}
const tokenData = await tokenResponse.json();
return tokenData.access_token; // アクセストークンを返す
}
// ----------------------------------------------------------------------
// 3. GA4 データ取得関数
// ----------------------------------------------------------------------
async function fetchGa4Data(accessToken, propertyId) {
const apiEndpoint = `https://analyticsdata.googleapis.com/v1beta/properties/${propertyId}:runReport`;
const response = await fetch(apiEndpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
dateRanges: [{ startDate: '30daysAgo', endDate: 'yesterday' }], // 昨日から30日間で取得する(必要に合わせて変更)
dimensions: [{ name: 'pagePath' }],
metrics: [{ name: 'screenPageViews' }],
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
limit: 100, // 100件以降の記事はPV0として処理する(必要に合わせて変更)
}),
});
if (!response.ok) {
throw new Error(`GA4 API Error: ${response.status} ${await response.text()}`);
}
const data = await response.json();
const pvData = data.rows?.map(row => ({
pagePath: row.dimensionValues[0].value,
pageView: parseInt(row.metricValues[0].value, 10),
})) || [];
return pvData;
}
// ----------------------------------------------------------------------
// 4. microCMS コンテンツID解決関数
// ----------------------------------------------------------------------
// `/blog/記事スラッグ/`という構造の場合(必要に合わせて変更)
function resolveContentIdFromPath(pagePath) {
try {
const cleanPath = new URL('<https://dummy.com>' + pagePath).pathname;
const parts = cleanPath.split('/').filter(p => p.length > 0);
// 1. '/blog/ID' の構造であるか確認
if (parts.length < 2 || parts[0] !== 'blog') {
return null;
}
const slug = parts[1];
// マップを使って、スラッグからコンテンツIDを取得
const actualContentId = contentIdToSlugMap[slug];
if (!actualContentId) {
// マップに存在しない(microCMSに記事が存在しない)場合は無視
console.log(`[IGNORE] Slug ${slug} not found in microCMS map.`);
return null;
}
// microCMSのコンテンツIDを返す
console.log(`[RESOLVED] Slug ${slug} -> ID: ${actualContentId}`);
return actualContentId;
} catch (e) {
console.error(`Path parsing error for ${pagePath}: ${e}`);
return null;
}
}
// ----------------------------------------------------------------------
// 5. microCMS コンテンツ更新関数
// ----------------------------------------------------------------------
async function updateMicroCMS(pvData, env) {
let updatedCount = 0;
const errors = [];
// 1. GA4データをスラッグをキーとするマップに変換
const ga4SlugPvMap = {};
pvData.forEach(item => {
const slug = item.pagePath.split('/').filter(p => p.length > 0)[1];
if (slug) {
ga4SlugPvMap[slug] = item.pageView;
}
});
// 2. microCMSの全記事マップ (contentIdToSlugMap) をループ
for (const slug in contentIdToSlugMap) {
if (contentIdToSlugMap.hasOwnProperty(slug)) {
const actualContentId = contentIdToSlugMap[slug];
// 2で取得したGA4のデータにあればその値、なければ 0 を設定 (リセット)
const newPageView = ga4SlugPvMap[slug] || 0;
// PATCHリクエストのURLを構築
const microcmsUrl = `${env.MICROCMS_API_URL}/${MICROCMS_ENDPOINT_NAME}/${actualContentId}`;
const updatePayload = {
pageView: newPageView,
lastUpdatedPV: new Date().toISOString()
};
const response = await fetch(microcmsUrl, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-MICROCMS-API-KEY': env.MICROCMS_API_KEY,
},
body: JSON.stringify(updatePayload),
});
if (response.ok) {
updatedCount++;
} else {
errors.push({ contentId: actualContentId, status: response.status, body: await response.text() });
}
}
}
if (errors.length > 0) {
console.error(`microCMS Update Errors: ${JSON.stringify(errors)}`);
}
console.log(`microCMSのコンテンツ ${updatedCount} 件を更新しました。`);
return updatedCount;
}
// ----------------------------------------------------------------------
// 6. Workerのメインハンドラー (Cron Triggers用)
// ----------------------------------------------------------------------
export default {
async scheduled(controller, env, ctx) {
try {
console.log('--- GA4 Ranking Updater Started ---');
// 1. microCMSからIDマップを事前取得
await fetchContentMap(env);
// 2. GA4 Access Tokenの取得
const accessToken = await getAccessToken(
env.GA4_SERVICE_ACCOUNT_CREDENTIALS,
env.GA4_PRIVATE_KEY_BASE64
);
// 3. GA4データ取得
const pvData = await fetchGa4Data(accessToken, env.GA4_PROPERTY_ID);
console.log(`GA4から ${pvData.length} 件のデータを取得しました。`);
// 4. microCMSコンテンツの更新
const updatedCount = await updateMicroCMS(pvData, env);
console.log(`microCMSのコンテンツ ${updatedCount} 件を更新しました。`);
console.log('--- GA4 Ranking Updater Finished ---');
} catch (error) {
console.error('致命的なエラーが発生しました:', error);
}
}
};
Hauptlogik des Worker-Codes:
- Besorgen Sie sich im Voraus eine Karte.Alle microCMS-Artikel
idUndslugKarte vorab laden. - GA4-Datenerfassung: Ruft die kumulierten Seitenaufrufe der letzten 30 Tage ab.
- AktualisierungslogikFalls in GA4 Daten vorhanden sind, aktualisieren Sie diese mit dem neuen PV; andernfalls aktualisieren Sie den PV.
0Zurücksetzen auf.
3-3. Cron-Einstellungen (Datenaktualisierung)
Cron-Trigger in den Triggerereignissen der Einstellungen0 15 * * *Es wird täglich um Mitternacht ausgeführt und spiegelt die Daten des Vortages in microCMS wider.
Schritt 4: Cloudflare Workers erstellen und konfigurieren (Build-Trigger)
4-1. Erstellen von Mitarbeitern und Festlegen von Umgebungsvariablen
Um Datenaktualisierungen und -bereitstellungen zu trennen, verwenden wir Worker, die speziell für den Website-Aufbau zuständig sind.build-triggerErstellen Sie es mit einem Namen wie:
In UmweltvariablenCLOUDFLARE_PAGES_BUILD_HOOKLegen Sie die Build-Hook-URL für Cloudflare Pages fest auf
4-2. Worker-Code (Build-Trigger)
Im Pages-Build-HookPOSTImplementieren Sie den Code, der die Anfrage sendet.
export default {
async scheduled(controller, env, ctx) {
// 環境変数からビルドフックURLを取得
const buildHookUrl = env.CLOUDFLARE_PAGES_BUILD_HOOK;
if (!buildHookUrl) {
console.error("FATAL: CLOUDFLARE_PAGES_BUILD_HOOK environment variable is not set.");
return;
}
// 2. ビルドフックURLにPOSTリクエストを送信
const response = await fetch(buildHookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
// 3. 結果の確認
if (response.ok) {
console.log("✅ Cloudflare Pages Build Triggered Successfully.");
} else {
console.error(`❌ Build Trigger Failed! Status: ${response.status} ${response.statusText}`);
}
},
};
4-3. Cron-Einstellungen (Standortbereitstellung)
Cron-Trigger in den Triggerereignissen der Einstellungen10 15 * * *Da wir den Abschluss von Schritt 3 abwarten möchten, bevor wir die Bereitstellung durchführen, starten wir die Bereitstellung jeden Tag 10 Minuten später um 0:10 Uhr.
Zusammenfassung
Auch wenn es weniger schnell geht als die Einrichtung einer Ranking-API, halte ich eine Dauer von etwa 10 Minuten für akzeptabel. Die Kosteneffizienz, keine API zu nutzen, die Möglichkeit, es im kostenlosen Tarif von Cloudflare auszuführen, sowie die einfache Konfiguration mit Cloudflare sind große Vorteile!
Er wechselte von Desktop-Publishing in die Webwelt und entwickelte sich schnell zu einem wahren Meister seines Fachs mit umfassenden Kenntnissen in Markup, Frontend-Design, Webentwicklung und Barrierefreiheit. Seit der Gründung von Liberogic ist er in verschiedenen Bereichen aktiv und gilt im Unternehmen mittlerweile als wandelndes Lexikon. In letzter Zeit beschäftigt er sich intensiv mit Effizienzsteigerungen durch den Einsatz von Eingabeaufforderungen und fragt sich: „Können wir uns bei der Barrierefreiheit stärker auf KI verlassen?“ Seine Technologien und sein Denken entwickeln sich stetig weiter.
Futa
IAAP-zertifizierter Spezialist für Webzugänglichkeit (WAS) / Markup-Ingenieur / Frontend-Entwickler / Webdirektor