Los clientes a menudo nos piden que mostremos las clasificaciones de sus artículos.
Al mostrar clasificaciones de artículos de microCMS en un sitio Jamstack, es común crear una API dedicada.
Sin embargo, si desea implementarlo pero no hay más API disponibles,Escribe las visitas a la página directamente en el contenido del artículo existenteTambién existe este enfoque.
¡Esta vez, presentaremos un ejemplo de este enfoque en un sitio publicado en SSG usando microCMS + Cloudflare Pages!
Sin embargo, Cloudflare se ha dado a conocer al público en general de una manera extraña, pero en realidad es algo que hemos estado probando en nuestro sitio web desde hace un tiempo, ¡en respuesta a las solicitudes de nuestros clientes!https://www.liberogic.jp/topics/
¿Cuándo es adecuado este enfoque?
- Si desea mostrar una clasificación de los 10 mejores en la barra lateral o en el pie de página
- Cuando no se requiere rendimiento en tiempo real
- No hay espacio para la cuota de API de microCMS
- Si desea una implementación sencilla mediante la generación de sitios estáticos (SSG)
Paso 1: Preparar el microCMS (extensión de esquema y control de webhook)
1-1. Ampliación del esquema de la API
Agregue dos campos de clasificación al punto final del artículo existente.
pageView(Número): almacena las páginas vistas acumuladas de los últimos 30 días obtenidas de GA4.lastUpdatedPV(Fecha y hora): Registra la fecha y hora en que se actualizó el PV por última vez.
1-2. Configuración de permisos de clave API
En la configuración de la clave API a utilizarPATCHMarcar la casilla.
1-3. Configuración de webhooks (una solución alternativa para compilaciones continuas)
Para evitar que se produzcan múltiples compilaciones al actualizar el recuento de páginas vistas de cada artículo, agregue lo siguiente al activador del webhook:Publicación de contenido (operaciones de API)" Desmarcar ".
Paso 2: Configurar la autenticación en Google Cloud Console
Para acceder desde Cloudflare Workers,Cuenta de servicioyAPI de datosPrepare lo siguiente:
2-1. Habilitación de la API
En su proyecto de Google Cloud Console,Google Analytics Data APIPermitir
2-2. Creación de una cuenta de servicio y una clave
Cree una cuenta de servicio para la integración de GA4 y copie la dirección de correo electrónico.
En la pestaña Claves, haga clic en "Agregar clave" y luego en "Crear una nueva clave", luego cree y descargue una clave con el tipo de clave JSON.
2-3. Concesión de permisos GA4
En la sección "Administración de acceso" de su propiedad GA4, agregue la dirección de correo electrónico indicada en 2-2 como usuario y otórguele privilegios de "Visor" o superiores.
Paso 3: Crear y configurar Cloudflare Workers (adquisición de datos GA4)
3-1. Creación de trabajadores y configuración de variables de entorno
trabajadoresga4-ranking-updaterCrea uno nuevo con un nombre como
Establezca las variables de entorno desde la configuración. Registre la clave como secreta.
|
MICROCMS_API_KEY |
Clave API de microCMS |
|---|---|
|
MICROCMS_API_URL |
https://[ID].microcms.io/api/v1 |
|
GA4_PROPERTY_ID |
Identificación de propiedad GA4 |
|
GA4_SERVICE_ACCOUNT_CREDENTIALS |
Todo el contenido del archivo de clave JSON |
|
GA4_PRIVATE_KEY_BASE64 |
Del valor de clave privada JSON |
3-2. Trabajador de actualización de datos GA4
Haga clic en "Editar código" y cambie el contenido de worker.js a lo siguiente:
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);
}
}
};
Lógica principal del código del trabajador:
- Obtenga un mapa con antelación:Todos los artículos de microCMS
idyslugObtener previamente el mapa. - Adquisición de datos GA4:Obtiene el PV acumulado de los últimos 30 días.
- Lógica de actualización:Si hay datos en GA4, actualícelos con el nuevo PV, si no, actualice el PV
0Restablecer a.
3-3. Configuración de cron (actualización de datos)
Los activadores de Cron en la configuración activan el evento0 15 * * *Se ejecuta todos los días a medianoche y refleja los datos del día anterior en microCMS.
Paso 4: Crear y configurar Cloudflare Workers (activadores de compilación)
4-1. Creación de trabajadores y configuración de variables de entorno
Para separar las actualizaciones y las implementaciones de datos, utilizamos trabajadores dedicados a la creación de sitios.build-triggerCréalo con un nombre como:
En variables de entornoCLOUDFLARE_PAGES_BUILD_HOOKEstablezca la URL del gancho de compilación de páginas de Cloudflare en
4-2. Código de trabajo (activador de compilación)
En el gancho de construcción de páginasPOSTImplementar el código que envía la solicitud.
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. Configuración de cron (implementación del sitio)
Los activadores de Cron en la configuración activan el evento10 15 * * *Como queremos esperar a que se complete el paso 3 antes de ejecutar la implementación, comenzaremos la implementación 10 minutos más tarde a las 12:10 a. m. todos los días.
resumen
Aunque es menos inmediato que preparar una API de posicionamiento, creo que es aceptable si toma unos 10 minutos. La rentabilidad de no consumir una API y poder ejecutarla en la versión gratuita de Cloudflare, así como la sencilla configuración que se puede realizar con Cloudflare, son grandes ventajas.
Pasó de la maquetación al mundo web y rápidamente se convirtió en un maestro de la artesanía, con un dominio del marcado, el diseño front-end, la dirección y la accesibilidad. Ha estado activo en diversos campos desde la fundación de Liberogic y ahora es un diccionario viviente dentro de la empresa. Recientemente, se ha obsesionado con explorar mejoras de eficiencia mediante indicaciones, preguntándose: "¿Podemos confiar más en la IA para la accesibilidad?". Su tecnología y su pensamiento siguen evolucionando.
Futa
Especialista certificado en accesibilidad web (WAS) de IAAP / Ingeniero de marcado / Ingeniero de interfaz / Director web