microCMS

Next.jsのOn-demand ISRで、ビルド不要の高速配信を実現する

シンハラ

この記事は公開後、1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、シンハラです。
普段はmicroCMSでカスタマーエンジニアを担当しております。

先日、Next.jsのバージョン12.1が公開されました!
Next.jsはVercel社が開発している、Reactベースの高機能フロントエンドフレームワークです。
今回のバージョンアップの目玉となったのが、On-demand ISRという機能になります。

今回はこちらの機能を活用して、コンテンツの高速配信をしつつ、ビルド時間を大幅短縮する手法を紹介したいと思います。

前提

On-demand ISRの説明の前に、前提となるISGとISRに触れたいと思います。

ISGとは?

ISGとは、Incremental Static Generationの略で、ページアクセス時にオンデマンドでページをビルドする手法のことです。

SGの場合、あらかじめ全てのページをビルドします。よって大量のページを処理する場合、ビルド時間がとても長くなるという欠点がありました。また動的なパラメータに対しては、あらかじめ値を知ることができないため、SSRで対応する必要がありました。

ISGを利用すると、事前のページビルドが必要ないため、ビルド時間は短縮できますし、アクセス時にビルドを行うため、動的なパラメータをキーにしたページ生成にも対応できるようになりました。

ISRとは?

ISRとは、Incremental Static Regenerationの略で、ISGにページ再生成の仕組みを追加した手法になります。

ISGの場合、ページのビルドは一回しか行われないため、データの更新があった際は、再度ビルドをかける必要があり、データが継続的に更新されるページには採用できませんでした。

ISRではrevalidateというプロパティを設定することで、再生成するまでの秒数を指定し、指定時間後にアクセスがあった際にページを再ビルドします。またstale-while-revalidateと呼ばれる考え方を採用しているため、再ビルドが完了するまではCDNにある古いページの情報が返却されます。これによってユーザーは常に、CDNのキャッシュを利用してページを閲覧することになるので、快適にブラウジングすることが可能です。

本題

On-demand ISRとは?

さて、ここでようやく本題に入ります。
前のセクションで記載したISRには一つ大きな問題点があります。それは、更新したコンテンツをすぐにページに反映できないということです。

ISRはrevalidateに設定した秒数の後のページアクセスによって更新されるため、確認のためにはその時間を待つ必要があります。こちらは設定する秒数を小さくすることで影響を少なくできるものの、データに変更がない場合でもサーバーサイドで再生成処理がたくさん実行されることになり、効率が悪くなり、料金面にも影響があります。

今回のアップデートで搭載されたOn-demand ISRを使うことで、任意のタイミングでページの再生成を行うことができるので、データの更新に合わせてページを再生できるようになりました!
microCMSを利用している場合は、コンテンツの更新に合わせてWebhookを発行できる機能があるので、これを利用して再生成を行うようにすると、効率よく運用を行うことができます。

実例

今回は私の個人開発のプロジェクトである、タミスタッツを利用して、本機能を紹介したいと思います。

タミスタッツは、大人気格闘ゲーム「大乱闘スマッシュブラザーズシリーズ」のオンライン大会である「タミスマ」の結果をまとめたサイトです。アーキテクチャはNext.js + microCMS + Vercelで、SGを行なっています。



タミスマは2~3日に1回ペースで行われているため、継続的にデータ更新を行う必要があり、microCMSを使って管理を行なっています。

またプレイヤーごとの成績などもまとまっていて、これらのページはmicroCMSのデータが更新されるたびに、サイト全体の再ビルドを行う必要がありました。

今回は、On-demand ISRを活用して、「大会成績が追加されたら、該当プレイヤーのページを更新する」という処理を作りたいと思います。

手順1 : Webhookの設定

microCMSではコンテンツに関する操作のタイミングで、Webhookを実行することができます。
また「カスタム通知」という設定を使うことで、任意のURLに対してPOSTリクエストを実行できます。

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

再生成用のURLについては、内部的にしか利用しないため、基本的には公開しない方が良いかと思います。
またその上でも、第三者によるアクセスを防止するため、シークレットという値を設定しています。
こちらを設定すると、X-MICROCMS-Signatureというヘッダーに、シークレット値とリクエストボディの内容から生成されたハッシュ値が設定されますので、こちらをもとにAPI側で認証を行います。

手順2 : APIの実装

On-demand ISRによる再生成処理は、ビューを返却する必要がないので、APIとして実装したいと思います。Next.jsの場合、pages/apiに作成することがお作法となっているので、該当ディレクトリに任意のファイルを作成します。

import { isCollectSignature } from 'libs/crypto'

export default async function handler(req, res) {
  try {
    if (!isCollectSignature(req.headers['x-microcms-signature'], req.body)) {
      return res.status(401).send('Invalid token')
    }

    const playerId = req.body.contents.new.publishValue.player.id
    await res.unstable_revalidate(`/players/${playerId}`)

    return res.status(200).send()
  } catch (err) {
    return res.status(500).send('Error revalidating')
  }
}


処理としては、受け取ったX-MICROCMS-Signatureをもとに、microCMSからのリクエストかを検証しています。
(具体的なコードについては、こちらのドキュメントが参考になります!)

リクエストボディには、以下の様な内容が入っています。

{
  service: 'tami-stats',
  api: 'tournament-result',
  id: '5oyaci2iu534',
  type: 'edit',
  contents: {
    old: {
      id: '5oyaci2iu534',
      status: [Array],
      draftKey: null,
      publishValue: [Object],
      draftValue: null
    },
    new: {
      id: '5oyaci2iu534',
      status: [Array],
      draftKey: null,
      publishValue: {
        id: '5oyaci2iu534',
        createdAt: '2022-03-21T00:03:27.230Z',
        updatedAt: '2022-03-22T03:05:55.021Z',
        publishedAt: '2022-03-21T00:03:27.230Z',
        revisedAt: '2022-03-22T03:05:55.021Z',
        tournament: {
          id: '5utda_x0g4zt',
          hoge: 'hoge'
        },
        player: {
          id: 'n6s70lqmhw6',
          hoge: 'hoge'
        },
        result: [
          '優勝'
        ],
        characters: [Array]
      },
      draftValue: null
    }
  }
}

oldが更新前のデータになり、newが更新後のデータとなります。
今回は更新後のデータを利用したいため、contents.new.publishValue内にあるプレイヤーIDを抽出し、該当ページを再生成しています。

動作デモ

これでコンテンツ更新時に自動的に再生成処理が実行されるようになりました。
実際にやってみましょう!

今回はこちらのページに、新しい大会結果を追加したいと思います。

microCMSより大会成績を入力し、公開します。

数秒後、追加した結果がページ反映されていることが確認できました!


以下が一連の流れのアニメーションとなります。

これでSGであっても、データの反映待ちをする必要がなくなりますね!

補足

  • On-demand ISRは現在Beta版の機能となっております。私が触っている限りでもやや挙動が怪しい部分があるため、本番環境への採用はもう少し様子を見たほうが良いかもしれません。
  • 今回は省略していますが、既存で登録されていた値を削除した場合、該当データに関連するページも更新する必要があります。その場合はcontents.old.publishValueの値を利用しましょう。


おわりに

以上、Next.jsのOn-demand ISRのご紹介でした。

On-demand ISRを活用することによって、コンテンツ更新のタイミングでビルドを行う必要がなくなるため、編集者はSSRに近い運用イメージで即時確認、ユーザーはSGならではの高速なページ表示といった、両者の良いとこどりな構成を実現できます🥰

また従来でもNext.jsをSSRとして動かし、手前にCDNを置く構成によってパフォーマンス最適化をはかることはできましたが、その場合だとJSONファイルをプリフェッチできないという欠点がありました。ISRの場合、プリフェッチが可能ですので、クライアントサイドでのページ遷移も高速に行われるというメリットがあります。

一方、On-demand ISRの弱点としては、「どのデータが更新されたらどのページを更新する」という設計をあらかじめ行なう必要がある点が挙げられます。こちらが面倒な場合は、通常のISRで運用するのが良さそうです。

今回はマイナーバージョンでの追加機能ということで、そこまで話題にはなっていないようですが、今後間違いなく注目される技術だと思います。是非一度おためしください!
-----

こちらの事例は、03/24(木)に開催予定の「ジャムジャム!!Jamstack_6」で発表予定です!
オンラインにて、無料で参加できますので、ぜひイベントページをチェックしてみてください😀

-----

microCMSは日々改善を進めています。
ご意見・ご要望は管理画面右下のチャット、公式Twitterお問い合わせからお気軽にご連絡ください!
引き続きmicroCMSをよろしくお願いいたします!

まずは、無料で試してみましょう。

APIベースの日本製ヘッドレスCMS「microCMS」を使えば、 ものの数分でAPIの作成ができます。

microCMSを無料で始める

microCMSについてお問い合わせ

初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。

お問い合わせ

microCMS公式アカウント

microCMSは各公式アカウントで最新情報をお届けしています。
フォローよろしくお願いします。

  • X
  • Discord
  • github

ABOUT ME

シンハラ
microCMSでカスタマーエンジニアを担当しています。 プライベートではNext.js / Nuxt.js とHeadless CMSを組み合わせた開発をメインで行なっています👨‍💻