Klanten vragen ons vaak om de rangschikking van hun artikelen weer te geven.
Bij het weergeven van artikelranglijsten van micorCMS op een Jamstack-site is het gebruikelijk om een speciale API te maken.
Als u het echter wilt implementeren, maar er is geen API meer beschikbaar,Schrijf paginaweergaven rechtstreeks in bestaande artikelinhoudDeze benadering bestaat ook.
Deze keer laten we een voorbeeld van deze aanpak zien op een site die gepubliceerd is op SSG met behulp van microCMS + Cloudflare Pages!
Cloudflare is echter op een vreemde manier bekend geworden bij het grote publiek, maar dit is iets dat we al een tijdje op onze website testen, naar aanleiding van verzoeken van onze klanten!https://www.liberogic.jp/topics/
Wanneer is deze aanpak geschikt?
- Als u een top 10-rangschikking in de zijbalk of voettekst wilt weergeven
- Wanneer realtime-prestaties niet vereist zijn
- Er is geen ruimte voor het microCMS API-quotum
- Als u een eenvoudige implementatie wilt met behulp van statische sitegeneratie (SSG)
Stap 1: MicroCMS voorbereiden (schema-uitbreiding en webhook-besturing)
1-1. Het API-schema uitbreiden
Voeg twee rangschikkingsvelden toe aan het bestaande artikel-eindpunt.
pageView(Getal): Slaat de cumulatieve paginaweergaven op voor de afgelopen 30 dagen, verkregen uit GA4.lastUpdatedPV(Datum en tijd): Registreert de datum en tijd waarop de PV voor het laatst is bijgewerkt.
1-2. API-sleutelmachtigingen instellen
In de instellingen voor de API-sleutel die u wilt gebruikenPATCHVink het vakje aan.
1-3. Webhook-instellingen (een tijdelijke oplossing voor continue builds)
Om te voorkomen dat er meerdere builds plaatsvinden bij het bijwerken van het aantal paginaweergaven voor elk artikel, voegt u het volgende toe aan de webhook-trigger:Inhoud publiceren (API-bewerkingen)"Uitvinken".
Stap 2: Authenticatie configureren in de Google Cloud Console
Om toegang te krijgen vanuit Cloudflare Workers,ServiceaccountEnGegevens-APIBereid het volgende voor.
2-1. API inschakelen
In uw Google Cloud Console-project,Google Analytics Data APIInschakelen
2-2. Een serviceaccount en sleutel aanmaken
Maak een serviceaccount voor GA4-integratie en kopieer het e-mailadres.
Klik op het tabblad Sleutels op 'Sleutel toevoegen' en vervolgens op 'Een nieuwe sleutel maken'. Maak en download vervolgens een sleutel met het sleuteltype JSON.
2-3. GA4-machtiging verlenen
Voeg in het gedeelte 'Toegangsbeheer' van uw GA4-eigenschap het e-mailadres dat u bij punt 2-2 hebt genoteerd toe als gebruiker en verleen deze persoon de rechten 'Kijker' of hoger.
Stap 3: Cloudflare Workers maken en configureren (GA4-gegevensverzameling)
3-1. Workers aanmaken en omgevingsvariabelen instellen
Werknemersga4-ranking-updaterMaak er een nieuwe aan met een naam als
Stel de omgevingsvariabelen in via de instellingen. Registreer de sleutel als geheim.
|
MICROCMS_API_KEY |
microCMS API-sleutel |
|---|---|
|
MICROCMS_API_URL |
https://[ID].microcms.io/api/v1 |
|
GA4_PROPERTY_ID |
GA4-eigenschaps-ID |
|
GA4_SERVICE_ACCOUNT_CREDENTIALS |
De volledige inhoud van het JSON-sleutelbestand |
|
GA4_PRIVATE_KEY_BASE64 |
Van de JSON private_key-waarde |
3-2. GA4 Gegevensupdatewerker
Klik op 'Code bewerken' en wijzig de inhoud van worker.js naar het volgende:
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);
}
}
};
Hoofdlogica van de Worker-code:
- Vraag vooraf een kaart aan: Alle microCMS-artikelen
idEnslugHaal de kaart vooraf op. - GA4-gegevensacquisitie: Geeft het cumulatieve aantal paginaweergaven van de afgelopen 30 dagen weer.
- Logica bijwerken: Als er gegevens in GA4 staan, werk deze dan bij met de nieuwe PV. Als dat niet zo is, werk dan de PV bij.
0Opnieuw instellen.
3-3. Cron-instellingen (gegevens bijwerken)
Cron-triggers in de instellingen triggergebeurtenis0 15 * * *Het wordt elke dag om middernacht uitgevoerd en geeft de gegevens van de vorige dag weer in microCMS.
Stap 4: Cloudflare Workers maken en configureren (build triggers)
4-1. Workers aanmaken en omgevingsvariabelen instellen
Om gegevensupdates en implementaties te scheiden, gebruiken we Workers die speciaal zijn bedoeld voor het bouwen van sites.build-triggerGeef het een naam als:
In omgevingsvariabelenCLOUDFLARE_PAGES_BUILD_HOOKStel de build-hook-URL van Cloudflare Pages in op
4-2. Werkcode (buildtrigger)
In de Pages-buildhookPOSTImplementeer de code die het verzoek verzendt.
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-instellingen (site-implementatie)
Cron-triggers in de instellingen triggergebeurtenis10 15 * * *Omdat we willen wachten tot stap 3 is voltooid voordat we de implementatie uitvoeren, starten we de implementatie elke dag 10 minuten later, om 00:10 uur, zodat er wat speling is.
samenvatting
Hoewel het minder snel klaar is dan het opzetten van een ranking-API, vind ik het acceptabel als het ongeveer 10 minuten duurt. De kosteneffectiviteit van het niet gebruiken van een API en de mogelijkheid om deze op de gratis versie van Cloudflare te draaien, evenals de eenvoudige configuratie die met Cloudflare kan worden voltooid, zijn grote voordelen!
Hij maakte de overstap van DTP naar de webwereld en ontwikkelde zich al snel tot een "meester in zijn vak" met een beheersing van markup, front-end design, richting en toegankelijkheid. Sinds de oprichting van Liberogic is hij actief geweest in diverse vakgebieden en is hij nu een wandelend woordenboek binnen het bedrijf. De laatste tijd is hij geobsedeerd door het onderzoeken van efficiëntieverbeteringen met behulp van prompts, met de vraag: "Kunnen we meer op AI vertrouwen voor toegankelijkheid?" Zijn technologie en denkwijze blijven zich ontwikkelen.
Futa
IAAP-gecertificeerde specialist in webtoegankelijkheid (WAS) / Markup-engineer / Front-end-engineer / Webdirecteur