Next.jsのISRを学ぶ

## Incremental Static Regenerationとは
「段階的な静的サイト再生成」と訳される`Static Site Generation(SSG)`のデメリットを補う機能です。

SSGのデメリットといえば、「事前にビルドしたページ内容しか返却しないこと」です。
要するにビルド後にデータベースが更新されたとしても、再ビルドしない限りはページ内容が更新されることはありません。

そこでISRを利用することでページにアクセスされる度に再ビルドさせることができます。
次回以降のアクセスから再ビルドで新しく生成されたページを返却するので、データベースの更新に合わせてページ内容を更新させたい場合に有効です。

## 基本的なISRの使い方

例）登録したタスクの一覧を表示するページ
/pages/tasks/index.tsx

```react
import { NextPage } from 'next';
import { TaskCard } from '~/components/atoms/task';
import { getAllTask } from '~/lib/task';

interface Task {
  // オブジェクトの型指定
}

type Props = {
  tasks: Task[];
};

const TaskListPage: NextPage<Props> = ({ tasks }) => {
  return (
      <div>
          {tasks.map((task) => (
            <TaskCard key={task.id} />
          ))}
      </div>
  );
};

export const getStaticProps = async() => {
  const tasks: Task[] = await getAllTask();

  return {
    props: { tasks },
    // ISRを利用するために必要な返り値
    revalidate: 3,
  };
}

export default TaskListPage;
```

上記のようにgetStaticPropsに定義した内容がビルド時に実行されます。次にgetStaticPropsから返却されたデータが同一ファイルのFunctionComponentに引数として渡されて、静的ファイルとして生成されます(つまり、SSGとして機能します)。

このときgetStaticPropsの返り値に`revalidate`を追加するとISRが機能するようになります。なんとたったの一行追加するだけで、ページにアクセスされる度に再ビルドしてくれるようになります。

revalidateに指定した数値の詳細は後述しますが、簡潔に言うと同時多発的に再ビルドが実行されないように制御するための秒数を指定しています。

## Dynamic RoutesでのISRの使い方

例）登録したタスクの詳細を表示するページ
/pages/tasks/[id].tsx

```react
import { useRouter } from 'next/router';
import { NextPage } from 'next';
import { TaskDetail } from '~/components/atoms/task';
import { ErrorPage } from '~/components/general'
import { getAllTaskId, getTaskById } from '~/lib/task';

interface Task {
  // オブジェクトの型指定
}

type Props = {
  task: Task;
};

const TaskDetailPage: NextPage<Props> = ({ task }) => {
  const router = useRouter();

  // getStaticPropsでまだデータ取得できていない場合にrouter.isFallbackがtrue
  if (router.isFallback) return <div>Loading...</div>;

  // データが存在しない場合
  if(!task) return <ErrorPage code={404}/>;

  return (
    <div>
      <TaskDetail task={task}/>
    </div>
  );
}

export const getStaticPaths = () => {
  const paths = await getAllTaskId();

  return {
    paths,
    fallback: true,
  };
}

export const getStaticProps = (paths: { params: { id: number } }) => {
  const task = await getTaskById(paths.params.id);

  return {
    props: {
      task,
    },
    revalidate: 3,
  };
}

export default TaskDetailPage;
```

[Dynamic Routes](https://nextjs.org/learn/basics/dynamic-routes)でISRを利用する場合、getStaticPathsの返り値にfallbackを指定する必要があります。fallbackはアクセスされたURLにページが存在しない場合の挙動を決定します。

上記のコードではfallbackにtrueを指定しており、trueの場合はページが存在しない場合に404エラーが発生しません。この場合、ページが存在しない場合の処理をコンポーネント内に記述する必要があります。

アクセスされたURLにページが存在しない場合のfallbackの指定による処理内容については下記を参考にしてください。詳細については[公式ドキュメント](https://nextjs.org/docs/basic-features/data-fetching#fallback-false)をご確認ください。

|値|処理内容|
|:--|:--|
|false|事前に用意したpagesディレクトリ配下の404ページを返却する|
|true|サーバーサイドでgetStaticPropsを呼び出して、動的にページを生成する。サーバーサイドでページ生成中は自身で記述しておいたフォールバックページを返却する。フォールバック中であるかの判定はuseRouterのisFallbackプロパティを利用する。|
|'blocking'|trueの場合と同じくサーバーサイドでgetStaticPropsを呼び出して、動的にページを生成する。ただし、ページを生成している間にフォールバックページを返却しません。|


## 再ビルドのタイミングについて
getStaticPropsで返却するrevalidateに指定する秒数は`同時多発的に再ビルドが実行されないように制御するための秒数`と説明しました。

例えば、1秒間に同一のページに1万回のアクセスがあった場合、全てのアクセスに反応して再ビルドしていたのではサーバーが対応しきれません。このように再ビルドするタイミングを制御する必要があるわけです。

それでは下記の図を見てください。ウェブページへの各アクセスに対して「レスポンス内容」と「再ビルドが実行されるのか」に着目して解説します。

![スクリーンショット 2021-08-15 1.26.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1716074/f27289a5-d58c-0d25-e313-0226776fbc7b.png)


### A1で実行される挙動
- 初回ビルドされたページが返却される
- 前回のビルドからデータベースの更新は存在しないので、再ビルドは実行されない

### A2で実行される挙動
- A1のアクセスではビルドが実行されていないため、初回ビルドされたページが返却される
- 前回のビルドからデータベースが更新されているため、再ビルドが実行される

### A3の大量アクセスで実行される挙動
- A3のアクセス時にA2のアクセスで実行されたビルドが完了していれば、D1の内容が反映されたページを返却するが、完了していなければ初回ビルドされたページを返却する
- 前回のビルドからデータベースが更新されていますが、再ビルド制御時間内なので再ビルドは実行されない
    - A2のアクセスで再ビルドが実行されてからrevalidateで指定した秒数の間（R2）はどれほど大量にアクセスされても再ビルドは実行されない

### A4で実行される挙動
- A2のアクセスで実行されたビルドではD２の内容は更新されていないため、D1の内容が反映されたページを返却する
- 前回のビルドからデータベースが更新されているので、再ビルドが実行される

### A5で実行される挙動
- D2の内容が反映されたページを返却する
- 前回のビルドからデータベースの更新は存在しないので、再ビルドは実行されない

A３で実行される挙動からわかるようにgetStaticPropsの返り値として指定するrevalidateの秒数については再ビルドが実行されてから何秒間は再ビルドを実行しないのかを指定していることになります。

この値が小さいほどビルドの回数が多くなり、サーバーに負荷がかかるものの最新の情報を反映しやすくなります。逆に値が大きいほどビルドの回数が減り、サーバーの負荷は減るものの最新の情報を反映しづらくなります。

この値については開発システムの利用用途や特徴を踏まえて決定すると良いでしょう。

## ISRだけでは出来ないこと
今回、ISRをSSGのデメリットである「事前にビルドした内容しか返却しないこと」を補う手段として紹介してきました。

ただし、ISRだけでは補えない課題も存在します。その代表格はDB更新後の初回アクセスで最新のページを表示できないことです。

DB更新後の初回アクセスは再ビルドのきっかけとなるものの、そのアクセス時点では最新のページ内容を得られませんから、一定のリアルタイム性が求められるウェブページでは使いづらそうです。

そこでお勧めしたいのはSSGとISRと[SWR](https://qiita.com/buttakyou/items/7ddf74ac8f63f5419822)の組み合わせです。
この組み合わせでウェブページを実装すれば、SSGの良さを引き出しながら一定のリアルタイム性を実現できるはずです。本記事では取り扱いませんが、お試しいただければ幸いです。