🍃

nextjsのISRを使うときのfallback指定について理解するまでの話

2021/04/28に公開

next.jsのISRを使おうとして「なんか全然うまく行かない」ってなってたのがやっと理解出来たのでメモ

問題編

とりあえず見様見真似でISRはrevalidateとfallbackつければ良いんだな?とやってみたところ、どうもpropsが空Objectになってしまうようで悩んでいた。

例えば下記のような場合、エラーが起きる

// pages/greeting/[name].js

const Page = (props) => {
  // ↓ここでエラー
  return <div>Hello {props.name.toUpperCase()}</div>
}

export const getStaticProps = async (req) => {
  return {
    props: {
      name: req.params.name
    },
    revalidate: 100
  }
}

export const getStaticPaths = async (req) => {
  return {
    paths: [],
    fallback: true
  }
}

export default Page
TypeError: Cannot read property 'toUpperCase' of undefined

   9 |   // }
  10 | 
> 11 |   return <div>Hello {props.name.toUpperCase()}</div>
     |                                ^
  12 | }

ここからよくよく調べたりドキュメントを読んだりしているとどうやらこれだけでは駄目らしいということがわかってきた。

解決編

まずfallback: trueの挙動として文字通り「fallback向けページ」を生成する。
fallbackページというのがはっきりしなかったが、これが「propsが空Object状態のページ」ということらしい。
このあとgetStaticPropsを呼び出し、そのJSONを当てはめた状態で再レンダリングするようだ。

TypeScriptで表せば、こういう違いになるだろう

// 通常のSSRの場合
const Page : FC<{ foo: string, baz: string}> 

//`fallback:true`にした場合
const Page : FC<{ foo: string, baz: string} | {}> 

ここから解決策を見ていく

解決策A: fallback: "blocking"を指定する

これは簡素。fallback: "blocking"にすればよい。


export const getStaticPaths: GetStaticPaths = async (req) => {
  return {
    paths: [],
    fallback: "blocking"
  }
}

必ず先にビルドが走るので、propsが入っている状態になる

解決策B: fallback: trueにしたままrouter.isFallbackの判定を入れる

fallback: trueでのパフォーマンスを維持したいなら、下記のようにrouter.isFallbackを利用する。

import { useRouter } from "next/router"

const Page = (props) => {
  const router = useRouter()

  if (router.isFallback) {
    return <div>Loading...</div>
  }

  return <div>Hello {props.name.toUpperCase()}</div>
}

こうするとまずisFallback=trueの状態で空オブジェクトになり、その後getStaticPropsを取得する処理が走る。

備考:next devではISRの検証は出来ない

In development (next dev), getStaticProps will be called on every request.

とのことで、devモードでは毎回getStaticPropsが呼び出される模様

Discussion