オンデマンド ISRの機能を使って、CMSにあるコンテンツの変更をトリガーにページを更新してみた。

2023/04/01に公開

はじめに

Next.jsのv12.1でオンデマンドISRが追加されました。これは、revalidate関数を使用することで、ページごとに手動で再生成できるようになる機能です。

Next.jsの公式ブログにある通り、ISRのデメリットは、revalidateで設定した時間を経過しなければ再生成が行われない点でした。

https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation

ですが、Next.jsのv12.1で追加されたオンデマンドISRを使用すれば、任意のタイミングでページの再生成を行うことができるようになりました。

ということで今回は、microCMSというツールを使って、CMS内で更新されたコンテンツがISRによる再生成を待たずに、オンデマンドISRによって再生成できるようにできるか試してみます。

下記で作成したサイトに対して行う。

https://zenn.dev/ryosuketter/scraps/cf116e7c28cedb

バージョンなどは上記スクラップにあります。

大まかな手順

です。なのでまずは

microCMS から Webhookを実行する設定

microCMSではコンテンツに関して何かしらしたらそれをトリガーにWebhookを実行することができます。

microCMSの「カスタム通知」という設定を使うことで、任意のURLに対してPOSTリクエストを実行できます。

今回はカスタム通知を以下のように設定しました。

第三者によるアクセスを防止、またはWebhook発行元がmicroCMSであることを検証可能にするため、シークレットという値を設定しています。

設定するとWebhookリクエストのリクエストヘッダにX-MICROCMS-Signatureが追加されます。この内容をもとにAPI側で認証を行います。

送受信されるデータは、microCMSがシークレット値とリクエストボディを組み合わせてSHA-256を使用したハッシュベースのHMACHash Based Message Authentication Code)で暗号化します。

シークレット値の生成などは公式通りに実施しました。

https://document.microcms.io/manual/webhook-setting#hb2d39bd6cc

これで完了です。

ちなみにこのSwitch

一時的にWebhookの設定を有効/無効にしたい場合に用いるそうです。

https://document.microcms.io/manual/webhook-setting#hb2d39bd6cc

Next.js で APIの作成

続いてAPIの実装をします。

公式のルール通り実装します。

https://nextjs.org/docs/api-routes/introduction

pages/api/project.tsに実装していきます。今回は対象が私の場合projectページなだけで、ここは任意に書き換えてもいいです。

project.ts
// Next.jsのAPIルートで使用されるリクエストとレスポンスの型をインポート
import type { NextApiRequest, NextApiResponse } from 'next'

const crypto = require('crypto')

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') return res.status(405).send('Method not allowed')
  if (!req.headers['x-microcms-signature']) return res.status(401).send('Invalid signature')

  const signature = req.headers['x-microcms-signature'] as string
  // シークレット値とリクエストボディをハッシュ化
  const expectedSignature = crypto
    .createHmac('sha256', process.env.MICROCMS_SIGNATURE)
    .update(JSON.stringify(req.body))
    .digest('hex')

  if (signature !== expectedSignature) return res.status(401).send('Invalid signature')

  try {
    await res.revalidate('/projects')
    return res.status(200).send('Revalidated')
  } catch (err) {
    return res.status(500).send('Error revalidating')
  }
}
環境変数の設置もね
.env.local
MICROCMS_SIGNATURE="micoCMSでシークレット値として設定した値です"

これまでのISRは revalidate で指定した時間が経過されるまで反映されませんでした。

上記に書いたAPIルートで、revalidate('/projects')を実行するとgetStaticPropsを使用しているprojectsページのキャッシュを再生成してくれます。

これで、ISRによるページ再生成を待たずに任意のタイミング再生成できます。

ちなみにgetStaticProps内のrevalidateも効くので指定時間後の再生成もできます。

シークレット値とリクエストボディをハッシュ化に関して

この部分に関して追記します。これはあとで署名(signature)を検証するために変数に格納しています。

そのために、cryptoモジュールのcreateHmacメソッドを使用して、HMACオブジェクトを作成しています。

このメソッドで、指定したアルゴリズム(ここではSHA-256)とシークレット値(process.env.MICROCMS_SIGNATURE)を使用して、HMACオブジェクトを作成しました。

続いて、HMACオブジェクトupdateメソッドを使用して、リクエストボディのJSON文字列を更新しています。

続いて、digestメソッドを使用して、HMAC値を16進数表記の文字列に変換しています。このメソッドは、HMACオブジェクトの内部状態から最終的なHMAC値を生成し、その値を16進数表記の文字列に変換しています。

expectedSignature変数に格納し、最終的に検証(microCMS経由かどうかを)するために使用します。

検証

自分は、以下の検証を通じて、オンデマンドISRが意図通り動いていることを確認しました。

ローカルにて

  • ブラウザからアクセスをする(例えば今回ならhttp://localhost:3000/api/projects
        - Method not allowedが返ることを確認(①)
  • ローカル環境に対してPostmanを使用してPOSTリクエストをする
        - リクエストヘッダーに(以下②)
    • 何も指定しないでPOSTリクエストをする
      • Invalid signatureが返ることを確認
    • keyはx-microcms-signature、valueは誤った値でPOSTリクエストをする
      • Invalid signatureが返ることを確認
    • keyはx-microcms-signature、valueは正しい値でPOSTリクエストをする
      • Invalid signatureが返ることを確認
        • なぜか?JSON.stringify(req.body)←この中身が空なので

本番環境にて

  • 次は本番で試します
    • その前に、vercelにも環境変数を設定することを忘れずに
    • ISRによる更新と混同しないようにrevalidateの値を120(2分)とかに指定しておくといいでしょう
  • ①と②の手順をして同じ結果になることを確認
    • 最後に、microCMSからコンテンツの更新を加えて
      • VercelのlogsのFunctionをAPIのパスのやつだけに絞って確認します
        • 何かしら間違いがなければ 200 が返ってコンテンツが再生性(更新)されているでしょう

以上です。

今から1年前の2022年2月に発表された機能で古いバージョンアップの検証ではありますが、自分のサイトでオンデマンドISRを使って、コンテンツの(ほぼ)即時更新ができるようにしました。

編集側が更新結果を(ほぼ)即時更新できる。かつサイトパフォーマンスもいい。良いとこどりですね。

(ほぼ)即時更新....感覚 5秒前後?

まだまだ初学者なので、誤りがあればご指摘ください!

参考

https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation

https://nextjs.org/blog/next-12-1

YouTube

https://zenn.dev/shimabukuromeg/articles/955cea236650d4

https://blog.microcms.io/on-demand-isr/

Discussion