😽

On-demand ISRの機能を試してみた

2022/02/28に公開

はじめに

12.1のリリースで、もっともリクエストの多かった機能、On-demand ISRが導入されたらしいので試してみました。

https://twitter.com/leeerob/status/1494351093924564992

TL;DR

  • SSG/ISRで生成した静的ファイルが、どのタイミングで再生成するか試してみたことをまとめた記事です。(revalidate で指定した値を過ぎたてアクセスしたり、unstable_revalidate を実行したり、などいろいろ試してみた結果をまとめてます)
  • 検証で作ってみたサイトは こちら
  • サンプルコードは こちら

On-demand ISRとは

On-demand ISRは、12.1のリリースにてベータ版で入った機能ですが、ISRについてもあらためて。

ISRとは

  • まずはドキュメントです。

https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration

https://nextjs.org/docs/api-reference/data-fetching/get-static-props#revalidate

  • ISRは段階的に静的ファイルを生成する仕組みです。一度生成したファイルを指定した時間経過後にリクエストすると再生成してくれます。

  • getStaticProps には revalidate というオプションがありますが、ここで指定した時間(秒)を経過後、ページにアクセスすると一度生成したファイルを再生成してくれます。

  • リクエストを受け取った際、stale-while-revalidateの挙動をとり、キャッシュが存在する場合キャッシュに存在する情報を返します。そのあと、バックグラウンドでページを再生成し、キャッシュを更新して、次のリクエストから更新した情報を返します。

  • Vercelのドキュメントにあったこの図の説明がすごくわかりやすかったです。

  • こちらの記事もすごくわかりやすかったです。

https://zenn.dev/catnose99/articles/8bed46fb271e44

  • コード例
export async function getStaticProps() {
  const currentTime = dayjs().tz();
  const createdAt = currentTime.format(formatStyle);
  const nextCreatedAt = currentTime.add(revalidate, 's').format(formatStyle);

  return {
    props: {
      createdAt,
      nextCreatedAt,
    },
    revalidate: 60,
  };
}

On-demand ISRとは

  • まずはドキュメントです。

https://vercel.com/docs/concepts/next.js/incremental-static-regeneration#on-demand-revalidation-(beta)

  • これまでのISRは revalidate で指定した時間が経過されるまでは、キャッシュされた情報が返されるので、データを更新してもすぐ反映されないかったのですが、On-demand ISRは指定した時間の経過を待たずに手動でキャッシュのpurgeとページの再生成ができるようになりました。

  • APIルートで、unstable_revalidate を実行するとgetStaticPropsを使用する個々のページのキャッシュを再生成してくれます。

  • コード例

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data | string>
) {
  try {
    await res.unstable_revalidate('/');
    return res.json({ revalidated: true });
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating');
  }
}

試したこと

今回試したことです。

ページ生成時に `getStaticProps` を使い、ページが生成されたタイミングの時刻を取得して表示させるサイトを準備した

  • 実装サンプル。createdAt がページが生成されたタイミングの時刻
  • revalidate は60秒で試した
export async function getStaticProps() {
  const currentTime = dayjs().tz();
  const createdAt = currentTime.format(formatStyle);
  const nextCreatedAt = currentTime.add(revalidate, 's').format(formatStyle);

  return {
    props: {
      createdAt,
      nextCreatedAt,
    },
    revalidate: revalidate,
  };
}
`revalidate` に指定した時間内でアクセスした場合は、ページの再生成は行われず、キャッシュされたデータが返されることの確認した(サンプルサイトの「いま表示してるHTMLが作られた時刻」で確認できる)

以下、22:47:40 と 22:48:06 にそれぞれアクセスした時の画像ですが、「いま表示してるHTMLが作られた時刻」は同じ時間なので、revalidate に指定した時間内でアクセスした場合は、ページの再生成は行われず、キャッシュされたデータが返されることを確認できた

  • 22:47:40 にアクセスした際の今表示してるHTMLが作られた時刻

  • 22:48:06 にアクセスした際の今表示してるHTMLが作られた時刻

  • レスポンスヘッダーが x-vercel-cache: HIT になってて、Edgeサーバーのキャッシュからのレスポンスだということがわかります。

The response was served from the edge

https://vercel.com/docs/concepts/edge-network/x-vercel-cache#hit

`revalidate` に指定した時間経過後アクセスした場合は、いったんキャッシュされたデータが返されますが、裏でページが再生成されてるので、その次アクセスした際にデータが最新になってることの確認した
  • 以下、revalidate に指定した時間経過後アクセスした際の画像です。stale-while-revalidateの動きをし、まずキャッシュにある情報を返すので、「いま表示してるHTMLが作られた時刻」は「このページにアクセスした時刻」よりも前の時刻になっています。revalidate で指定した時間経過後にアクセスしてるので、裏でぺーじの再生成が行われており、次回アクセスした際は、以下の画像の「このページにアクセスした時刻」は「いま表示してるHTMLが作られた時刻」になっているはずです。

  • 以下、次回アクセスした際の画像です。前回、「このページにアクセスした時刻」は「いま表示してるHTMLが作られた時刻」になっていることがわかります。

  • revalidate に指定した時間経過後アクセスした際の画像をみると、レスポンスヘッダーが x-vercel-cache: STALE になっています。これは、Edgeサーバーのキャッシュからのレスポンスを受け取りつつ、バックグラウンドでオリジンサーバーに最新のページを生成します。

The response is stale (served from the edge). A background request to the origin was made to get a fresh version. (Learn more)

https://vercel.com/docs/concepts/edge-network/x-vercel-cache#stale

`unstable_revalidate` を実行すると、`revalidate` で指定した時間にかかわらずページが再生成されることの確認した
  • 以下、23:53:35 に生成されたページを表示してる画像です

  • 以下、Call Unstable_revalidate API ボタンをクリックして res.unstable_revalidate('/'); を叩いた際の画像です

  • 以下、res.unstable_revalidate('/'); を叩いた後、改めてページにアクセスした際の画像です。「いま表示してるHTMLが作られた時刻」は、23:53:35から23:53:53になっていることから、revalidateで指定してる値の60秒を待たずに、res.unstable_revalidate('/'); の実行がトリガーとなり、ページの再生成が行われてるとわかります。

サンプル

以下、試したことで使ったコードとサイトです。

  • コード

https://github.com/shimabukuromeg/next-isr-sample

おわりに

  • 個人的にですが、普段は認証が必要なWebアプリケーションを作ることが多く、Full CSRの構成でNext.jsを使ってるので、今回ISRを試しながら、段階的に静的ページを生成する仕組みを学べて勉強になったのがよかったです。

  • 今回リリースされたOn-demand ISR以前のISRだと、指定した時間経過後のリクエストをトリガーにrevalidateしてページを再生成していましたが、On-demand ISRの登場で任意のタイミングでキャッシュのpurgeとぺーじの再生成できるようになったので、最新の値の反映がいつ反映されるかわからないというような問題がなくなりそうで良さそうだと思いました。

  • Vercelのキャッシュの仕組み、エッジネットワークに興味湧いたのでドキュメントもっとちゃんと読もうと思いました。

https://vercel.com/docs/concepts/edge-network/overview

https://vercel.com/docs/concepts/edge-network/caching#stale-while-revalidate

https://vercel.com/docs/concepts/edge-network/x-vercel-cache

参考

参考にしたリンクたち。すごく勉強になりました。

https://qiita.com/Sinhalite/items/762f192459994b915380

https://qiita.com/thesugar/items/47ec3d243d00ddd0b4ed

https://zenn.dev/akino/articles/78479998efef55

https://blog.microcms.io/think-about-jamstack-2021/

https://zenn.dev/musou1500/articles/a7d8061d1f5b05

https://zenn.dev/terrierscript/articles/2021-04-28-nextjs-isr-fallback

https://zenn.dev/catnose99/articles/8bed46fb271e44

unstable_revalidate の実装

https://github.com/vercel/next.js/blob/c1fd2ca79f52119557415487a8ff863da2f89000/packages/next/server/api-utils/node.ts#L272-L319

GitHubで編集を提案

Discussion