【React / Next.js】Next.jsにおけるレンダリングの基礎

# <font color="green">Next.jsにおけるレンダリングの種類</font>

<br>

**【 主な種類 】**

<br>

| 種類 | 正式名称 |
|:-:|:-:|
| CSR | クライアントサイドレンダリング |
| <font color="red">SSR</font>  | <font color="red">サーバーサイドレンダリング</font> |
| <font color="red">SG</font>  | <font color="red">静的サイト生成</font>  |
| ISR | インクリメンタル静的再生成 |

<br>

**Next.jsでは、赤文字の２種が主に用いられることが多い。**

<br>
<br>

# CSR（クライアントサイドレンダリング）

データフェッチ（外部APIに対してデータをとりにいく処理）や、ルーティングの全てがクライアント上で行われる。

Next.jsでは、クライアント側でのみ行いたい処理は`useEffect`で囲む。

※Next.jsではあまり利用されない概念。

<br>
<br>

# <font color="red">SSR（サーバーサイドレンダリング）</font>

サーバーにリクエストがきたタイミングで動的にHTMLを生成する手法。

外部APIへのデータの取得や、コンポーネントのPropsの値を決定する処理を行い、HTMLを生成してクライアント側に返却する。

<br>
<br>

# <font color="red">SG（静的サイト生成）</font>

`ビルド時`にデータフェッチやPropsの値の決定を行い、HTMLを構築する。

クライアントからリクエストが来たら、サーバー側で構築せず、生成済みのHTMLを返却する。

<br>
<br>

# ISR（インクリメンタル静的再生成）

ビルド時にHTMLを構築。

`一定時間後`にアクセスがあった場合、生成済みHTMLを返却しつつ、サーバー側でHTMLを構築。

` 次のアクセス時に`最新のHTMLを返却する。


### Next.jsの基本構成

**静的ページは`SG`で行い、**
**動的に生成する必要があるページは`SSR`で行うという構成にするのが一般的。**

<br>
<br>


# <font color="green">【SSR】 getServerSideProps関数 について</font>

SSRの時に実行される関数のこと。

実行は`Node.js`上で実行される。


```react
export async function getServerSideProps(context) {
    // 処理内容
    // 引数のcontextには、requestとresponseの情報が載ってくる。
}


// 例
 export default function SSR({ message }) {
   return <h3>{message}</h3>;
 }

 export async function getServerSideProps(context) {
   return { props: { message: "hello from server side props" } };
 }
```

# <font color="green">【SG】 静的サイトを生成してみる</font>

## ◆ jsファイルの作成 + 関数コンポーネントの定義

<br>
<br>

**【 ディレクトリ階層 】**

```
root/
　├ start/
　　 　 └ pages/
　 　     └ 020_SG/
　  　　　       └ index.js
```

<br>
<br>

**【 index.js 】**

```react
export default function IndexPage() {
  return <h3>SG</h3>;
}
```

<br>
<br>

## ◆ ビルド

ディレクトリ階層の`start`フォルダ地点で、ビルドを実行。

<br>

**【 実行コマンド 】**

<br>

`package.json`にbuildコマンドがあること。

```javascript
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "json-server": "npx json-server -w ./mock/db.json -p 4030"
  },
```

<br>
<br>

実行が完了すると以下のようなログが表示される。

<br>
<br>

![スクリーンショット 2023-06-20 20.39.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/82563b87-d61a-6ff5-1a14-b7aa64512662.png)


また、`○` はSGが実行され、 `入` はSSRが実行されたことを示している。

<br>
<br>

## ◆ エクスポート

ビルドが完了したら、`export`コマンドを実行する。

そのためにまず、exportコマンドをpackage.jsonに設定。

<br>
<br>

```javascript
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",

    "export": "next export", // ← 新規追加

    "json-server": "npx json-server -w ./mock/db.json -p 4030"
  },
```

<br>
<br>

追加したら、先ほどと同様に`start`フォルダ地点でコマンド実行。

```
npm run export
```

<br>
<br>

exportが正常に完了すると、以下のようなログが出力される。

<br>
<br>

![スクリーンショット 2023-06-20 20.47.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/10b1a365-f147-1a79-46f9-76b9380e68ea.png)

これで生成完了。

生成されたHTMLは`outディレクトリ`内に格納されている。

<br>
<br>

![スクリーンショット 2023-06-20 20.50.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/91763a88-dcad-5a98-7086-55865ba3b497.png)

<br>
<br>

## <font color="red">ビルド・エクスポート時の注意点</font>

### 【 getServerSideProps関数がある場合のエラー 】

<br>

以下のようなエラーが表示された場合。

```
Error occurred prerendering page "/010_SSR". Read more: https://nextjs.org/docs/messages/prerender-error

Error: Error for page /010_SSR: pages with `getServerSideProps` can not be exported. See more info here: https://nextjs.org/docs/messages/gssp-export
    at /Users/start/node_modules/next/dist/export/worker.js:209:27
    at async Span.traceAsyncFn (/Users/start/node_modules/next/dist/trace/trace.js:79:20)
info  - Exporting (5/5)
Error: Export encountered errors on following paths:
        /010_SSR
    at /Users/start/node_modules/next/dist/export/index.js:395:19
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Span.traceAsyncFn (/Users/start/node_modules/next/dist/trace/trace.js:79:20)
```

<br>
<br>

`getServerSideprops`は、Node.js上（サーバー上）でしか実行されないものなので、この設定が残っているとビルドで失敗する。

よって、この設定があるファイルは、getServerSidePropsを削除 or コメントアウトで消さなければいけない。

<br>
<br>

---

### 【 Imageタグのエラー 】

<br>

以下のようなエラーがビルド時に表示された場合。

```
Error: Image Optimization using Next.js' default loader is not compatible with `next export`.
  Possible solutions:
    - Use `next start` to run a server, which includes the Image Optimization API.
```

この場合の対処法は以下の通り。

<br>

#### ◆ next.config.jsに設定を追加

```react
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
 // 以下の設定を追加
  images: {
    loader: "custom",
  },
};

module.exports = nextConfig;

```

<br>
<br>

#### ◆ Imageタグを使用しているファイルで、Imageタグに以下を記載


```react
<Image
    loader={({ src }) => src }
/>
```

詳しいことは不明。

Imageタグのデフォルトは`Nodejs サーバー`を使用し、`画像最適化 API `のもと行われるため、SGでは処理できない。

<br>
<br>

そのため、next.config.jsでimagesの設定を`custom`に設定し、`クラウドプロバイダー(?)`という別の取得方法に変更することで解決するということらしい...。

<br>
<br>

Imageタグにloaderをpropsとして設定することで、デフォルトのloaderを上書きできるらしく、上記はその上書きをおこなっている。

<br>
<br>

デフォルトのloader関数は以下の通り。

<br>
<br>

```react
import Image from 'next/image'

const myLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

const MyImage = (props) => {
  return (
    <Image
      loader={myLoader}
      src="me.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}
```

https://nextjs.org/docs/app/api-reference/next-config-js/images

https://nextjs-ja-translation-docs.vercel.app/docs/api-reference/next/image

<br>
<br>


#### ◆ 作成したHTMLで遷移ができない。

作成したHTMLに試しにアクセスをしてみる。

VSCodeのLive Serverを使用し、アクセスしてみる。すると、

<br>
<br>

![スクリーンショット 2023-06-20 21.48.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/5455fb43-9242-077b-3a36-f5b4d0d4720c.png)

<br>
<br>

となってしまう。理由はリンクが違うからである。（http://127.0.0.1:5500/020_SG.html が正しく、リンクに拡張子がない。）

TOPページで遷移できたのは、HTMLを取得しているのではなく、ブラウザ上でJavascriptが実行されて遷移できているだけ。

この問題を解決するには、next.config.jsに以下の設定を追加する。

<br>
<br>

```react
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,

  // 追加
  trailingSlash: true,

  images: {
    loader: "custom",
  },
};

module.exports = nextConfig;

```

<br>
<br>

この設定はリンクの末尾にスラッシュを追加するという設定である。

この状態で再度ビルド＋エクスポートを実行すると、

<br>
<br>

![スクリーンショット 2023-06-20 22.01.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/2062240a-b23f-971c-635c-53c92778c7d7.png)

<br>
<br>

設定の追加前と比べ、index.htmlにディレクトリ階層が発生する。

この状態で先ほどと同じようにアクセスすると、

<br>
<br>

![スクリーンショット 2023-06-20 22.04.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/e7599603-7829-7df4-0555-56c42e876e36.png)

<br>
<br>

アクセスが可能になっている。
これはは、サーバーの挙動に理由がある。


`/`がつくと、システムはまずリクエストの値に該当する`ファイル`もしくは`フォルダ`を探し出し、`フォルダ`があった場合は、次の`/`の後に続く値に該当するファイルを探し出す。
そしてファイルの指定がない場合は、デフォルトで`index.html`を表示する。

この挙動により、表示が可能になる。


<br>
<br>

# <font color="green">【SG】 getStaticProps関数について</font>

SGで関数コンポーネントにPropsを渡すには、`getStaticProps`関数を使用する。

<br>
<br>

```react
export async function getStaticProps() {
    // 処理内容
    // 引数のcontextには、requestとresponseの情報が載ってくる。
}

// 例
export default function IndexPage({ message }) {
  return <h3>SG: {message}</h3>;
}

export async function getStaticProps() {
  return {
    props: { message: "Hello" },
  };
}
```

<br>
<br>

またこの`getStaticProps`は`getServerSideProps`とは違い、`基本的にはビルド時に`実行される。

<br>
<br>

# <font color="green">【SG】 getStaticPaths関数について</font>

この関数は、SGかつダイナミックルーティングを利用するときに用いられることが多い。

<br>
<br>

**【 ディレクトリ階層 】**

```
root/
　├ start/
　　 　 └ pages/
　 　     └ 020_SG/
　  　　　       └ [id].js
　  　　　       └ index.js
```

<br>
<br>

**【 [id].js 】**

```react
export default function Page({ id }) {
  return <h3>このページは{id}</h3>;
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: false,
  };
}

export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
  };
}
```

<br>
<br>

`getStaticPaths`関数のpathは、`getStaticProps`のcontext内に格納されている。
以下はビルド実行した結果。

<br>
<br>

```
[    ] info  - Generating static pages (0/7){
  params: { id: '2' },
  locales: undefined,
  locale: undefined,
  defaultLocale: undefined
}
{
  params: { id: '1' },
  locales: undefined,
  locale: undefined,
  defaultLocale: undefined
}
info  - Generating static pages (7/7)
info  - Finalizing page optimization  

Page                                       Size     First Load JS
┌ ○ /                                      1.1 kB         84.5 kB
├   └ css/adf6895ec356eb64.css             653 B
├   /_app                                  0 B            83.4 kB
├ ○ /010_SSR                               276 B          83.7 kB
├ ● /020_SG                                301 B          83.7 kB
├ ● /020_SG/[id] (302 ms)                  320 B          83.7 kB
├   ├ /020_SG/1
├   └ /020_SG/2
├ ○ /404                                   194 B          83.6 kB
└ λ /api/hello                             0 B            83.4 kB
+ First Load JS shared by all              83.4 kB
  ├ chunks/framework-4556c45dd113b893.js   45.2 kB
  ├ chunks/main-d4e32c6306a72f6e.js        30.8 kB
  ├ chunks/pages/_app-01dced7ad17e1737.js  6.6 kB
  ├ chunks/webpack-69bfa6990bb9e155.js     769 B
  └ css/3a770aacb0f6edfc.css               652 B
```

<br>
<br>

`getStaticProps`は、`getStaticPaths`のリターンのpathの数だけ実行される。

※console.logの実行結果が2回表示されていることからわかる。

exportを実行すると、outフォルダに以下のようにpathで渡した値がファイル名になっていることがわかる。

![スクリーンショット 2023-06-20 23.37.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/2dc742f5-27b5-862a-20d1-4256faeebca7.png)

![スクリーンショット 2023-06-20 23.40.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/e3a97c45-9f66-cbfb-fa44-638f8fcece77.png)

![スクリーンショット 2023-06-20 23.40.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/eda6eee1-1d32-c112-72b5-c8f4e268afa2.png)

<br>
<br>

## ◆ getStaticPathsのfallbackについて

こちらの設定はダイナミックルーティングで作成されていないルーティングに対してアクセスした場合の処理についての設定である。

## 【 まず前提として 】

先ほどの説明で、`getStaticProps`は「基本的にビルド時に実行される」と説明をしたが、ビルド時以外にも実行されるケースがある。

これは、SGを実行する方法によって変わる。

<br>
<br>

### ◆ １つ目　build + export 
<br>

こちらは先ほどから実行している方法。
この方法の特徴は、export後に出力される`outフォルダ`の中のHTMLを参照しているところ。


<br>
<br>

### ◆ 2つ目　<font color="red">build + start</font>

こちらはbuildした後に`npm run start`コマンドを実行してSGを実行する方法。

この方法では`outフォルダ`は参照されず、通常のNext.js通り、`pages`を参照したルーティングになる。

<br>
<br>

## ◆ 挙動確認

### 【 fallback: falseの場合 】

ルートを存在しない　http:\/\/localhost:3000/020_SG/1/　でアクセスしてみると、

<br>
<br>

![スクリーンショット 2023-06-21 0.22.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/076ba6db-1069-2763-4db0-86be83e126c0.png)

<br>
<br>

404ページを返す。

<br>
<br>

### 【 fallback: trueの場合 】

trueの場合、作成されていないリンクにアクセスした場合の処理を考えることができる。
この時、`useRouter`を使用する。

<br>
<br>

```react
import { useRouter } from "next/router";

export default function Page({ id }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return <h3>このページは{id}</h3>;
  }
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: true,
  };
}

export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
  };
}
```

<br>
<br>

**【 解説 】**


```react
export default function Page({ id }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return <h3>このページは{id}</h3>;
  }
}
```

<br>
<br>

作成されていないリンクにアクセスした場合、Node.js側で新しくページの作成を開始する。
この時`fallback`は`false`の状態であり、`router.isFallback`が`false`になるため、Loading...が画面に表示される。

作成されている間、随時`getStaticProps`がNode.js上で実行されている。

<br>
<br>

![スクリーンショット 2023-06-21 0.33.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/2468d887-2e9c-2a16-2ab7-048e949e20ce.png)

<br>

ページの作成が完了すると、`fallback`が`true`になるため、`router.isFallback`が`true`に変わり、作成されたページが表示される。

<br>
<br>

![スクリーンショット 2023-06-21 0.33.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/7eb7f449-bde2-1d58-8f14-c3c21070f49e.png)

<br>
<br>

また、一度作成されたページはプロジェクト内の`.next`フォルダ上に保存されるため、次回以降は作成される処理は流れない（`getStaticPropsは実行されない`）。

<br>
<br>

![スクリーンショット 2023-06-21 0.42.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/4cc95a3b-c0db-92db-0970-1988162ae21c.png)


<br>
<br>

### <font color="green">【 fallback: "blocking"の場合 】</font>

true/false以外にも、`blocking`という設定がある。

<br>
<br>

```react
import { useRouter } from "next/router";

export default function Page({ id }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return <h3>このページは{id}</h3>;
  }
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: "blocking",
  };
}

export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
  };
}
```

<br>
<br>

この`blocking`という設定では、`ページの作成が完了するまでレスポンスを待機させる`という設定になる。

<br>
<br>

# <font color="green">ISRの使い方</font>

ISRは最初の紹介の際、

`ビルド時にHTMLを構築。一定時間後にアクセスがあった場合、生成済みHTMLを返却しつつ、サーバー側でHTMLを構築し、次のアクセス時に最新のHTMLを返却する。`

という説明をした。これはつまり、
`SG と SSR　を組み合わせて`利用するということである。

## 実際の利用方法

<br>
<br>

```react
export async function getStaticProps(context) {
  console.log(context);
  return {
    props: {
      id: context.params.id,
    },
    // 追加
    revalidate: 5,
  };
}
```

実際の設定方法はとても簡単で、``getStaticProps`のreturnで返すオブジェクトに、

`revalidate:数値 `

というプロパティを数値を設定するだけ。

試しに`props`に日付を取得する変数を追加し、画面上で確認してみる。

<br>
<br>

```react
import { useRouter } from "next/router";

export default function Page({ id, date }) {
  const router = useRouter();
  if (router.isFallback) {
    return <h3>Loading ...</h3>;
  } else {
    return (
      <h3>
        このページは{id}
        <br />
        {date}
      </h3>
    );
  }
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: "blocking",
  };
}

export async function getStaticProps(context) {
  console.log(context);
  const date = new Date();
  return {
    props: {
      id: context.params.id,
      date: date.toJSON(),
    },
    revalidate: 5,
  };
}
```

この状態でビルド + スタートし、画面表示をしてみる。

<br>
<br>

![スクリーンショット 2023-06-22 0.46.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/9eaff38a-32da-daf3-f712-4206f098aa89.png)

初期表示ではこの値だが、先ほどの`revalidate`の値が`5`なので、
5秒後に再度リクエストを送信すると、新しい数値が表示されるようになる。

↓ 5秒後

![スクリーンショット 2023-06-22 0.46.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/bd4a0139-99f1-b1a3-47d7-c16e91e22b33.png)