【Next.js】SSG を利用したサイトアーキテクチャ

:::note info
追記（2022.05.21）

この記事の内容は既に古いです。
ReactやNext.jsのアップデートはとても早く、便利な機能が次々実装されています。
現時点でのNext.jsを使ったサイトアーキテクチャを詳しく知りたい場合は、以下のコースの受講をお勧めします。
:::

https://www.udemy.com/course/nextjs-supabase-web-tailwindcss/

この講師の方の他のコースを受講するとクーポンが発行されたり、またUdemyでは頻繁に値引きセールを行っているので、これらを利用して購入しましょう。（1500円程度で購入できます）

とはいえNext.jsの設計思想自体は変わりません。せっかくこの記事に訪れて頂いたので参考程度に読んでください。

<!-- ============================================ -->
# 概要
<!-- ============================================ -->
本記事は、Next.jsのSSGを利用した技術とサイトの設計指針についてまとめました。

※ 私自身、Next.jsについてはまだ勉強中で、その備忘録として書いています。

<!-- ============================================ -->
# SSG（Static Site Generation｜静的サイト生成）
<!-- ============================================ -->
Next.jsでは、**SSG（Static Site Generation｜静的サイト生成）**という技術が標準サポートされていて、それを使用することが推奨されています。SSGを使用すると、SEOに有利で、高速に表示されるサイトを作ることができます。

また、SSGを利用してAPIなど外部との通信が発生する処理（時間のかかる処理）を、サイトのビルド時に行い（Pre-fetch）、そのデータを埋め込んだHTMLをあらかじめサーバー側で生成することができます。

詳しく知りたい方は、公式ドキュメントを参照してください。英語ですが図説されているのでわかりやすいです。

**Pre-rendering and Data Fetching**

https://nextjs.org/learn/basics/data-fetching

日本語がいい！という方は、以下の動画シリーズがおすすめです。

https://www.youtube.com/watch?v=F77RUjHZEAc&list=PLX8Rsrpnn3IUGEyanrHYGjY1WOzNe7Jd-

<!-- ============================================ -->
# Deploy Hooks
<!-- ============================================ -->
Next.jsを開発しているVercel社は、Hostingサービスも提供しています。
このサービスでは、GitHubに連携してリポジトリ（mainなど）の更新に応じてリビルドする機能や、API経由でリビルドする**Deploy Hooks**が提供されています。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/529fe4a5-cfba-0a74-2ddb-7995e7451a1c.png)


<!-- ============================================ -->
# ISR（Incremental Static Regeneration｜段階的な静的サイト生成）
<!-- ============================================ -->
ユーザーがサイトに訪れたときにサイトをリビルドして、次に訪れたユーザーに最新のサイトを提供する機能です。

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

![ISR.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/578c2403-786a-004c-19ec-ff05b3168f4f.jpeg)

① <font color="Red">ユーザーA</font>が、<font color="Green">Vercelサーバー</font>にリクエストを送ります。
② <font color="Green">Vercelサーバー</font>は、レスポンスとしてホスティングされたサイト（HTML）を返します。
③ ②と同時に、<font color="Green">Vercelサーバー</font>は、更新のあるページ（ISRを指定したページ）だけをリビルドします。
④ <font color="Blue">ユーザーB</font>が、<font color="Green">Vercelサーバー</font>にリクエストを送ります。
⑤ <font color="Green">Vercelサーバー</font>は、レスポンスとして③で更新したサイト（HTML）を返します。

**revalidate**は更新頻度で、コード内でこれを指定することでISR機能を使うことができます。
上図では、<font color="Blue">ユーザーB</font>は最新のサイトを閲覧できますが、<font color="Red">ユーザーA</font>は古いサイトを閲覧することになります。このことから、ISRは、**閲覧回数が多いサイト向けの機能**です。
revalidateを指定することで、高頻度のサイトアクセスによる過剰な更新を防ぎ、サーバー負荷を減らします。（ゲームでいうところのスキルのクールダウンみたいな感じです）

### コード例
```.ts
export const getStaticProps: GetStaticProps = async () => {
	const tasks = await getAllTasksData();
	return {
		props: { tasks },
		revalidate: 3
	};
};
```

`getStaticProps`のreturnに、**revalidate**を指定することで、ISRに対応させたページにします。


<!-- ============================================ -->
# Client side Fetching（CSF）
<!-- ============================================ -->
クライアントサイドで、効率よくAPIでデータを取得する方法として、**SWR**があります。
キャッシュデータを使うことで過度なAPIリクエストを防ぎ、素早くサイトを表示させます。

https://swr.vercel.app/ja

これを使って何が嬉しいのかというと、ISRだけでは、<font color="Red">ユーザーA</font>は最新のサイトを閲覧できませんでした。
そこで、この**SWR**と組み合わせることによって、<font color="Red">ユーザーA</font>も最新のサイトを閲覧することができます。

https://swr.vercel.app/ja/docs/with-nextjs#%E4%BA%8B%E5%89%8D%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0

下図は、**SSG + ISR + CSF（SWR）**を組み合わせた例です。

![ISR+CSF.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/d8c33615-de2f-a7ae-f932-528c87417e62.png)
① <font color="Green">開発者</font>がサイトをデプロイします。
②②´ Vercelサーバーは、SSGのPre-fetchによりビルド時にAPI経由でデータを取得します。
③③´ <font color="Red">ユーザーA</font>がサイトにアクセスします。
③″ クライアントサイドで、API経由で最新のデータを取得します（Client side Fetching）。
④④´ ③のリクエストをトリガーにして、ISRによってリビルドされます。このとき、②´同様にAPI経由でデータを取得します。
※③″と④´でAPIからデータを取得するタイミングはほぼ同じなので、同じでデータになるはずです。（秒単位で更新されるデータの場合は差異が生まれるかもしれません）
⑤⑤´ <font color="Blue">ユーザーB</font>がサイトにアクセスします。
⑤″ ③″同様、クライアントサイドで、API経由で最新のデータを取得します（Client side Fetching）。
※<font color="Red">ユーザーA</font>がサイトに訪れてから、<font color="Blue">ユーザーB</font>が訪れるまでに、サーバーのデータが更新されていなければ、⑤´の内容と⑤″で取得する内容は同じになります。

### コード例
```.tsx
const fetcher = (url: string) => fetch(url).then<TaskType[]>(res => res.json());

type PropsType = {
	tasks: TaskType[];
};

const TaskPage: VFC<PropsType> = ({ tasks }) => {
	const { data, error } = useSWR(`${process.env.NEXT_PUBLIC_RESTAPI_URL}/api/list-task/`, fetcher, {
		initialData: tasks, // ― 2
		revalidateOnMount: true // ― 3
	});

	// 以降、得られた data を使った処理
	・・・
}

export default TaskPage

export const getStaticProps: GetStaticProps = async () => {
	const tasks = await getAllTasksData(); // ― 1
	return {
		props: { tasks },
		revalidate: 3 // ― 4
	};
};
```

① SSGのPre-fetchで、サーバーサイドでAPIデータを取得します。
② `useSWR`のオプション、`initialData`にサーバーサイド取得したAPIデータ（tasks）を渡します。
　これによって、クライアントサイドでデータが取得できるまでは、サーバーサイドで取得したデータを表示させます。
　※このとき、APIデータに更新があると、古いデータ → 新しいデータとレンダリングされるので一瞬だけ古いデータが表示されます。
③ `useSWR`のオプション、`revalidateOnMount`を指定することで、コンポーネントがマウントされたときに必ずデータを取得するようします。
④ ISRに対応させることで、次にサイトを表示したときに、②で発生していた古いデータが一瞬だけ表示される現象が発生しないようにします。

https://swr.vercel.app/ja/docs/options

<!-- ============================================ -->
# サイト アーキテクチャ
<!-- ============================================ -->
これらの技術を使うことで、API経由で取得する**データの更新頻度**、**APIの使用制限**、**サイトのアクセス頻度**に併せてサイトを設計することができます。

※ 以下のモデルは、あくまで一例です。

### 手動でリビルドする
API経由で取得するデータの更新に併せて、サイトを手動でリビルドします。

**条件・状況**
・API経由で取得するデータの更新頻度が低い場合（半年に1回、不定期など）
・APIの使用制限が厳しい場合（1時間あたりのリクエスト回数が決まっている、リクエスト回数に応じて料金が発生する）
・サイトのアクセス頻度が低い場合

### Deploy Hooks を使う
Deploy Hookに、**Cloud Functions（Cloud Functions for Firebase、 AWS Lambda など）**を組み合わせることで、定期的に自動でリビルドするようにします。（毎日24:00にリビルドするHookを作る）

**条件・状況**
・API経由で取得するデータの更新頻度がそこそこの場合（数周間に1回、1日に1回など）
・APIの使用制限が厳しい場合（1時間あたりのリクエスト回数が決まっている、リクエスト回数に応じて料金が発生する）
・サイトのアクセス頻度が低い場合

### ISR を使う
ISRの項目で紹介した設計を使います。

**条件・状況**
・API経由で取得するデータの更新頻度が高い場合（数時間に1回、数分に1回など）
・APIの使用制限が`そこそこ`厳しい場合（1時間あたりのリクエスト回数が決まっている、リクエスト回数に応じて料金が発生する）
・サイトのアクセス頻度が高い場合

### ISR + Client side Fetching を使う
Client side Fetchingの項目で紹介した設計を使います。

**条件・状況**
・API経由で取得するデータの更新頻度が高い場合（数時間に1回、数分に1回など）
・APIの使用制限がゆるい場合（自社製のAPIサーバーを使うなど）
・サイトのアクセス頻度が`そこそこ`高い場合


<!-- ============================================ -->
# まとめ
<!-- ============================================ -->
Next.js 面白い。

<!-- ============================================ -->
# 参考
<!-- ============================================ -->
この記事は、以下のコースの受講に伴って、備忘録としてまとめました。
もっと詳しく知りたい方は、是非受講してみてください。

https://www.udemy.com/share/103VXW3@rGA0PbPDiV7gQO2_jj99HZ-HhNmaKit_EJWM3yiDSP5vSFdDFi1Khhxy7NSIAwc5xg==/

https://www.udemy.com/share/1040nE3@Dj7rB5Aafz7BXQIN06LBKShYHhntgSna5ms_7dAvSLod_-WB8kdEiF9icZAHxUujWA==/