コンテンツの更新時にNext.jsでOn-Demand Revalidationを実行する

Next.jsでは Incremental Static Regeneration (ISR) という機能を利用して、再デプロイすることなく、サイトのコンテンツをページ単位で更新することが可能です。
特に On-Demand Revalidation の機能と、Newtの Webhook を利用することで、コンテンツ情報の更新をトリガーに、該当のページのみ、すぐにサイトを更新できます。

前提条件

  1. Incremental Static Regeneration・On-Demand Revalidationの基本的な概念について理解していること
  2. Next.jsの v12.2.0 以上を利用していること(v12.1.0からOn-Demand Revalidationは利用可能ですが、メソッド名等が異なります)

Incremental Static Regeneration・On-Demand Revalidationについて、詳細を知りたい場合はVercelの Incremental Static Regeneration のドキュメントをご確認ください。

概要

以下の流れで処理を行うものとします。

  1. コンテンツの更新
  2. Webhookでリクエストを送信(更新するページを特定する情報を含める)
  3. Next.jsに実装したrevalidate用のAPIが呼び出され、Webhookのbodyからどのページを更新するべきか読み取る
  4. 該当ページを更新する

1. 実装前の準備

実装を始める前に、以下のことを決めましょう。

  • コンテンツの更新時に、どのページを更新するか
  • 更新するページのパスを取得するために、コンテンツのどのフィールドの情報が必要か
  • リクエストが有効なものかどうやって検証するか(そもそも検証は必要か)

ここでは以下の想定で進めます。

  • ある記事が更新された場合に、記事の詳細ページ /articles/${slug} を更新する
  • slugの情報は、各コンテンツのslugというフィールドに設定されている
  • リクエストが有効か、クエリパラメータのsecretの値で検証する

2. Revalidation処理を実装する

Next.jsの API Routes の機能を利用して、Revalidation処理を実装します。
この処理では、以下のことを行います。

  • リクエストが有効なものか、クエリパラメータのsecretの値で検証する(MY_SECRET_TOKEN という環境変数を利用する)
  • Webhookのbodyからslugの値を取り出す
  • /articles/${slug} のページを更新する
// pages/api/revalidate.js

export default async function handler(req, res) {
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' });
  }

  try {
    const { slug } = req.body;
    await res.revalidate(`/articles/${slug}`);
    return res.json({ revalidated: true });
  } catch (err) {
    return res.status(500).send('Error revalidating');
  }
}

TypeScriptで実装する場合は、以下のようになります。

// pages/api/revalidate.ts

import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' });
  }

  try {
    const { slug } = req.body;
    await res.revalidate(`/articles/${slug}`);
    return res.json({ revalidated: true });
  } catch (err) {
    return res.status(500).send('Error revalidating');
  }
}

3. 環境変数を登録する

2でリクエストの検証を行うために、MY_SECRET_TOKEN という環境変数を利用しました。
Vercelを利用する場合、Settings > Environment Variables のページから環境変数を登録できます。Vercelでの環境変数の登録について、詳細はVercelの Environment Variables のドキュメントをご確認ください。

vercel_env_vars.jpg

4. Webhookを作成する

次にWebhookを作成し、2で作成したRevalidation処理を呼び出せるようにします。
1で決めた内容をもとに、このWebhookは以下の要件を満たすように作成します。

  • URLには、Revalidation処理を呼び出すURLを指定する
  • クエリパラメータにsecretの値を設定し、3で登録した値と一致させる
  • slug情報を持たせる

Newtの管理画面に入り、スペース設定 > Webhook のページから「作成」を押します。

4-1. URLの指定

クエリパラメータにsecretの値を含め、また2で作成したRevalidation処理を呼び出すために、以下のようなURLを指定します。

https://<your-site.com>/api/revalidate?secret=<token>

例えば、ドメインが「revalidate-sample-aaa-bbb.vercel.app」で、secretが「hogehoge」の場合、https://revalidate-sample-aaa-bbb.vercel.app/api/revalidate?secret=hogehoge となります。

ScreenShot2022-07-21at11.22.36.png

4-2. ペイロードの設定

ペイロードにはslugの情報を持たせる必要があります。
以下のように {"slug": "{ content.slug }"} と指定します。

ペイロードには変更後のコンテンツ情報を持たせることができます。コンテンツ情報全体を指定することも、今回のように特定のフィールドに絞ることも可能です。

ScreenShot2022-07-21at10.56.57.png

4-3. その他の設定

最後にステータスを「有効」にして、Webhookを作成します。
ヘッダーの設定は不要です。

5. 処理を確認する

最後に、コンテンツを更新した時、該当ページのみが更新されることを確認しましょう。
もし、うまくいかない場合は、Webhookのアクティビティログを確認して 200 が返ってきているか、Revalidation処理で指定しているパスが意図通りになっているか確認してみてください。

ScreenShot2022-07-21at11.09.58.png

以上で設定は終了です。
これで、通知対象のコンテンツの公開・非公開が変更された時、および公開コンテンツの更新された時に、該当ページのみ、素早く更新されます。

Newt Made in Newt