SITECORE MANIA 開発者向け技術ブログ

コードサンプル

チョク

チョク

コンテンツ更新でISRのキャッシュをクリアする方法

サイトコアのバージョン

■ 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のバージョンによって実装されている機能が異なります。

この記事を読んだ人はこちらの記事も読んでます

Sitecore導入に関するご相談・資料ダウンロード

導入をご検討・ご依頼の方や、サービスについてご不明点がございましたらお気軽にお問い合わせください。

Sitecoreの運用問題を解決する

既にSitecoreを導入しているお客様向けサービス