📝

【Next.js】【入門/チュートリアル】fallbackとIncremental Static Regeneration(ISR)

2021/02/23に公開

記事を書く動機

下記で、Next.jsの公式サイトのチュートリアルを行ったのですが、チュートリアルだけでは少しわかりづらかったfallbackIncremental Static Regeneration(ISR)について備忘も兼ねてスクラップから切り出して記事にしようと思いました。

【チュートリアルのスクラップ】
https://zenn.dev/yuki_yuki/scraps/5b28f6a278db3c

注意事項

Next.jsを始めたばかりなので、認識が間違っている部分、わかりずらい部分があるかもしれないです。
その場合は、コメントいただけますと幸いです。

Dynamic Routes

Dynamic Routesの概要

fallbackIncremental Static Regeneration(ISR)を理解するためには、まずDynamic Routesについて理解できていないといけない気がします。

Dynamic Routesはパスパラーメータによって動的にルーティングを変えたい時に使います。
例えば、下記のような2つのURLがあったとして、ルーティングをパスパラーメータによって動的に変えたい場合に使用します。

  • /posts/1
  • /posts/2

Dynamic Routesの実装方法

ファイル名に[]を使う

Dynamic Routesはファイル名に[]を使うことで実現できます。
例えば、下記のように[id].jsとすることで、id部分を動的にルーティングすること(/posts/1/posts/2など)が可能になります。

--project
  --pages
    -- posts
      -- [id].js

[].jsファイルの実装

また、Dynamic RoutesgetStaticPathsメソッドで予めパスパラーメータとして取りうる値を指定することでルーティングが可能となります。

もう少し詳しく説明すると、getStaticPathsメソッドでpathsfallbackを返しており、

  • pathsはパスパラーメータとして取りうる値を定義し、
  • fallbackは定義したpaths以外のアクセスがあった場合どのような処理を行うかを決めます。(詳しくは後述)
pages/posts/[id].js
import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

export async function getStaticPaths() {
return {
    // pathsが必須。
    paths: [
      {
        // パスパラーメータとして取りうる値をidをKeyにしたparamsObjectで定義する。
        params: {
          // `/posts/1`へのルーティングが可能。
          // idはファイル名に合わせる。今回は[id].jsなのでKeyが`id`となる
          id: '1',
        },
      },
      {
        params: {
          // `/posts/2`へのルーティングが可能。
          id: '2',
        },
      },
    ],
    // fallbackが必須。詳しくは後述。
    fallback: false,
  };
}

// 上記で定義したをparamsObjectをgetStaticPropsの引数で受け取る。
// `/posts/1`へのルーティングの場合、params = {id: '1'}
export async function getStaticProps({ params }) {
  // 受け取ったパスパラーメータをもとに処理を行う
}

export default Post

fallback

上記で説明したようにfallbackは定義したpaths以外のアクセスがあった場合どのような処理を行うかを決めます。
上記の例で言うと、pathsに定義されていない/posts/3などからアクセスがあった場合にどのような処理を行うかを決めるということです。

fallback: falseの場合

pathsに定義されていないパスからのアクセスがあった場合、404ページを返却します。
上記の例で言うと、pathsに定義されていない/posts/3からのアクセスがあった場合に404ページを返却します。

fallback:trueの場合

pathsに定義されていないパスからのアクセスがあったとしても、404ページが返却されません。
代わりに以下のようなフローで、新たに静的ファイルを作成し、クライアント側に返却します。
上記の例で言うと、

  1. /posts/3のパスがRequestとして来ます。
    pathsに定義されていないidであるが、fallback:trueなので、404ページを返却しません。
  2. 裏側でサーバー側がgetStaticPropsを実行し、id=3に紐づく静的なファイルを生成します。
  3. 生成が完了したら作成した静的ファイルを返します。
  4. これ以降の/posts/3は②で作成した静的ファイルを返します。

以下、サンプルコードです。

pages/posts/[id].js
import { useRouter } from 'next/router'

function Post({ post }) {
  // fallbackの状態を監視できるuseRouter()を使用
  const router = useRouter()

  // 上記でいう②が完了するまで、router.isFallback = trueとなり、ローディング画面をを表示
  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render post...
}

// Build時のみに実行する
export async function getStaticPaths() {
  return {
    // `paths`にid=1,2のみを定義。これらのパスはBuild時に生成される
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    // id=3を許容できるようにfallback: trueを設定する
    fallback: true,
  }
}

// Build時にはparams.id=1,2で実行
// もし、`/posts/3`が来た場合、裏側で非同期的にサーバー側が`getStaticProps`を実行
export async function getStaticProps({ params }) {
 // 受け取ったパスパラーメータをもとに処理を行う
}

export default Post

fallback:trueの注意点

②で生成された静的ファイルは、一度生成されてしまうと、内容の更新があったとしても静的ファイル自体の更新はされません。
(SSGの性質上当たり前なことだと思いますが...)
つまり、②で一度静的ファイルが作成されたら④以降ではその静的ファイルをずっと参照することになります。
更新後、更新した静的ファイルを参照するためにはIncremental Static Regeneration(ISR)(後述)を使用します。

Incremental Static Regeneration(ISR)

ISRは動的コンテンツを事前Buildせずに(全てのページを生成するのではなく)、ページにアクセスしたときに初めてBuildします。
ISRでBuildした内容には有効期限(revalidate)を設けることができ、有効期限を過ぎたページにアクセスされた場合は、前回ビルドされたコンテンツを返しつつも、裏側で再Buildをサーバー側にかけにいきます。

この機能が生まれた背景

ISRはNext.js 9.4から実装されたものです。
これ以前のSSGでのBuild方法は下記のようなデメリットがあり、それを解消するために生まれたようです。

  • 膨大な数(数億、数兆規模)の静的ページをSSGで一度にBuildする場合、データの取得やHTMLの作成が膨大になり、Buildに時間がかかる
  • ページが更新された際は再度全てのページをビルドしなおさないといけない

ISRは上記で説明したように、ページにアクセスしたときに初めてBuildするため、事前Buildでのページ数を減らすことができ、また、有効期限を過ぎたものは裏側で再Buildされるため、ページの更新を随時行うことができます。

ISRの実装方法

getStaticPropsの返り値にrevalidateを設定すればISRが使えるようになります。

function Post({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

// 初回Build時にサーバー側で実行
// revalidateで設定した時間を過ぎ、かつRequestが来た時にサーバー側で再実行される
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // revalidateを設定すればISRが使えるようになる。(単位は秒)
    revalidate: 1, 
  }
}

export default Post

最後に

チュートリアルの際に少しわかりづらかったfallbackIncremental Static Regeneration(ISR)についてまとめてみました。

冒頭でも述べたように、Next.jsを始めたばかりなので、認識が間違っている部分、わかりずらい部分があるかもしれないです。
その場合は、コメントいただけますと幸いです。

参考資料

Discussion