🦮

[NextJS]ISRのfallbackの挙動が難しかった

2023/02/06に公開

概要

ISRを使用した際のgetStaticPathsfallbackの各モードについて、挙動が難しかったのでまとめることにします。
理解に苦しんだので認識が違っていましたら、教えていただけると幸いです。

おそらく初めてISRの実装をする方はrevalidatepathに比べてfallbackの挙動を理解するのに苦労すると思います。

getStaticPathsのfallbackについて

getStaticPathsのfallbackのモードにはfalsetrueblockingの3種類あり、falseは理解できるがtrueblockingの挙動は若干難しいです。

・サンプルコード

import { NextPage, GetStaticPaths, GetStaticProps } from 'next';

interface Props {
  post: Post
}

const Page: NextPage<Props> = ({post}) => {
  return (
    <p>{post.title}</p>
  )
}

export const getStaticProps: GetStaticProps<Props> = async () => {
  const {post} = await fetchPost();
  
  return {
    props: {
      post
    },
    revalidate: 60,
  }
}

export const getStaticPaths: GetStaticPaths = async () => {
  return {
    paths: [
      { params: { id: '1' } },
    ],
    fallback: 'false',
  }
}

export default Page;

fetchPostの引数なしでpostを一件取得するというツッコミどころはありますが、そちらは一旦置いといてもらえると助かります。

falseの挙動

この例ですと

paths: [
  { params: { id: '1' } },
],
fallback: 'false',

でビルド時にpost/1のHTMLを生成しています。(revalidateは1分)
するとたとえばpost/2などビルド時にHTMLを生成していないページを指定(リクエスト)した際は404エラー(page)となります。

なのでダイナミックルートのページなどにpathを指定してfallbackをfalseに指定することは少ないと思います。

trueの挙動

ダイナミックルートなどのページではpathを指定せずtrueかblockingを指定します。

paths: [],
fallback: 'true'//or blocking,

この場合ビルド時にはどのpathのHTMLも生成せず、遷移時には外部データが取得されていない状態でHTMLをクライアントに返し、その後サーバで外部データを取得してHTMLを生成しレンダリングされたのち生成したHTMLをキャッシュします(1分間)。

流れを整理すると
①外部データがない状態のHTMLを表示
②サーバーサイドで外部データを取得しクライアントをレンダリングする
③②で生成したHTMLをキャッシュし次回以降はそのHTMLを返す
となります。

となるとPropsのpostは最初は空のオブジェクト({})になるのでこのままですと以下の箇所でtypeエラーとなります。

<p>{post.title}</p>

fallbacktrueの際の対処法

まず一つがpostが空の場合の分岐をクライアントで作ることが考えられます。

<p>{post ? post.title : '...fallback中'}</p>

またこれと似てはいますが、router.isFallbackの判定を入れる方法も考えられます。

import { useRouter } from "next/router"

interface Props {
  post: Post
}

const Page: NextPage<Props> = ({post}) => {
  const router = useRouter()

  return (
    {router.isFallback ? (
      <p>...fallback中</p>
    ) : (
      <p>{post.title}</p>
    )}
  )
}

そしてもう一つの対処法がblockingになります。

blockingの挙動

blockingはその名の通りfallbackをブロック(fallbackを行わない)します。
どういう挙動かというと、

paths: [],
fallback: 'blocking',

とした場合、ビルド時にどのpathのHTMLも生成されていないのはtrueと同じです。
ですが初回の遷移の挙動が異なります。

blockingの場合、初回遷移時にサーバー側で完全なHTMLが生成されるまでHTMLを返しません。
つまり、初回はSSRと同じ挙動になり、次回以降はキャッシュされたHTMLを表示します。

流れは以下のようになります
①遷移時(初回)にSSRで完全なHTMLを生成しクライアントに返却(キャッシュもする)
②次回以降キャッシュを表示

となります。

getStaticPropsで外部データの取得がエラーとなった際

これは僕がつまったところでもあるが、もしgetStaticPropsの中での外部データの取得が失敗した場合、 どういった挙動になるのかである。
公式ドキュメントに記載がありました。

If there is an error inside getStaticProps when handling background regeneration, or you manually throw an error, the last successfully generated page will continue to show. On the next subsequent request, Next.js will retry calling getStaticProps.

getStaticProps内でエラーが発生した場合、または手動でエラーをthrowした場合、最後に正常に生成されたページが引き続き表示されます。そして後続のリクエストでgetStaticPropsの呼び出しを再試行します

つまり外部データの取得に失敗した際は、エラーをthrowすれば前回までの正常に生成されたHTMLを表示してくれるらしい...
知らなかった...

ぜひうちではgetStaticPropsでのエラーハンドリングはこうしてるよ!などがありましたら教えてください!

参考
Next.js 10 リリースノート全訳! 画像の自動最適化、i18n対応、アナリティクス、Eコマースほか
nextjsのISRを使うときのfallback指定について理解するまでの話
NextJS公式

Discussion