サイトコアのバージョン
■ SitecoreAI
概要
SitecoreAIのコンテンツ更新およびパブリッシュするときに、ISRの性質上、一回ページにアクセスしないと、キャッシュの再生成が行われず、古いコンテンツが表示されてしまいます。
この記事では、SitecoreAIのコンテンツ更新でISRのキャッシュをクリアする方法について説明します。
変更方法
①まずは以下のドキュメントを参考にして、ExperienceEdgeにOnUpdateのWebhookを追加します。
https://doc.sitecore.com/sai/ja/developers/sitecoreai/experience-edge/webhook-objects/webhook-objects.html
追加するWebhookのbody例
{
"label": "isr-revalidate",
"uri": "https://<ドメイン>?secret=<secretトークン>",
"method": "POST",
"headers": {
"X-Revalidate-Token": <secretトークン>
},
"executionMode": "OnUpdate"
}
Webhookを追加したら、コンテンツをパブリッシュしたら、以下のようなjsonがサイトに送信されます。
{
"invocation_id": "6a271d7b-92de-4d14-8002-8fc97a46c290",
"updates": [
{
"identifier": <コンテンツのアイテムID>,
"entity_definition": "Item",
"operation": "Update",
"entity_culture": "ja-JP"
}
],
"continues": false
}
②次は、jsonを受信したら、ISRのキャッシュをクリアするAPIを実装します。
APIのコードサンプル
import type { NextApiRequest, NextApiResponse } from 'next';
import graphQLClientFactory from 'lib/graphql-client-factory';
const MY_SITE = {
siteName: 'sample',
rootPath: '/sitecore/content/sample/Home',
};
type EdgeUpdate = {
identifier: string;
entity_definition: string; // 'Item' | 'LayoutData' など
operation: string; // 'Update' | 'Delete'
entity_culture: string; // 'en', 'ja-JP' など
};
type EdgeWebhookBody = {
invocation_id: string;
updates: EdgeUpdate[];
continues: boolean;
};
function toRelativePath(sitecorePath: string, rootPath: string): string {
const relative = sitecorePath.slice(rootPath.length);
return relative.replace(/\/+$/, '') || '/';
}
async function getItemInfoById(
itemId: string,
language: string
): Promise<{ path: string } | null>{
const query = `
query ($itemId: String!, $language: String!) {
item(path: $itemId, language: $language) {
path
}
}
`;
try {
const client = graphQLClientFactory();
const data = await client.request<{
item: { path: string } | null;
}>(query, { itemId, language });
console.log(
'[revalidate] itemId:',
itemId,
'language:',
language,
'response:',
JSON.stringify(data)
);
if (!data?.item?.path) return null;
return { path: data.item.path };
} catch (err) {
console.error('[revalidate] GraphQL request failed:', JSON.stringify(err));
return null;
}
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const secret = req.query.secret;
if (secret !== process.env.REVALIDATE_SECRET) {
return res.status(401).json({ message: 'Invalid token' });
}
const body = req.body as EdgeWebhookBody;
const updates = body?.updates ?? [];
const myPaths = new Set<string>();
const skipped: { identifier: string; reason: string }[] = [];
for (const update of updates) {
try {
if (update.entity_definition !== 'Item' || update.operation.toLowerCase() === 'delete') {
skipped.push({
identifier: update.identifier,
reason: `unsupported: ${update.entity_definition}/${update.operation}`,
});
continue;
}
const language = update.entity_culture || 'ja-JP';
const itemInfo = await getItemInfoById(update.identifier, language);
if (!itemInfo) {
skipped.push({ identifier: update.identifier, reason: 'item not resolved' });
continue;
}
// 変更されたアイテム自身が自分のサイト配下なら追加
if (itemInfo.path.toLowerCase().startsWith(MY_SITE.rootPath.toLowerCase())) {
myPaths.add(toRelativePath(itemInfo.path, MY_SITE.rootPath));
}
} catch (err) {
skipped.push({ identifier: update.identifier, reason: (err as Error).message });
}
}
const revalidateResults: { path: string; ok: boolean; error?: string }[] = [];
for (const path of myPaths) {
try {
await res.revalidate(path);
revalidateResults.push({ path, ok: true });
} catch (err) {
console.error(`[revalidate] Failed to revalidate ${path}:`, err);
revalidateResults.push({ path, ok: false, error: (err as Error).message });
}
}
return res.json({
site: MY_SITE.siteName,
revalidated: true,
revalidatedPaths: Array.from(myPaths),
revalidateResults,
skipped,
continues: body?.continues ?? false,
});
}
③コンテンツをパブリッシュして、サイトのログの該当apiにレスポンスにキャッシュクリアするパスが表示されれば完了です。
以上になります。
※エントリーの内容・画像等は、公開時点での情報に基づきます。
※Sitecoreのバージョンによって実装されている機能が異なります。