【Next.js14】CSR・SSR・SSG・ISRの違いと実装方法

![giphy (16).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/173e6752-2e7e-6702-6b15-e25b17b54b18.gif)

Next.jsでは、レンダリング手法をCSR・SSR・SSG・ISRの中から選ぶことができます。

本記事では、それぞれの特徴に触れつつ、実装方法を紹介します。

## CSR （Client Side Rendering）
クライアントからのリクエストに対して、サーバーは空のHTMLとJavaScriptを返します。

このJavaScriptがブラウザ上で実行されることにより、実際に表示するHTMlをレンダリングします。

### メリット
- サーバーとの通信が初期遷移時のみに抑えられる
- ページ遷移が高速

### デメリット
- 初回読み込み時に全てのデータを一括して取得するので、ページが表示されるまでの時間が長い（アプリケーションの規模が大きくなればなるほど、時間が長くなる）
- JavaScriptはブラウザで実行されているので、ページ表示までの時間（JavaScriptの実行時間）が使用しているマシンスペックに依存してしまうので、全てのユーザーに安定した速度を提供できない
- サーバーから返却されるHTML自体は空の為、Webクローラーからページの内容が認識されず、SEOで不利になる

### 条件
- ファイルの先頭「`"use client"`」というキーワードが記述されている

### 実装例
```react
"use client";

import React, { useEffect, useState } from "react";

type Product = {
  id: string;
  title: string;
};

const CsrPage = () => {
  const [products, setProducts] = useState<Product[]>([]);

  const fetchData = async () => {
    const res = await fetch("https://dummyjson.com/products");
    const data = await res.json();

    setProducts(data.products);
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <>
      <h3>Built with CSR</h3>
      <br />
      <ul>
        {products.map((product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default CsrPage;
```
## SSR （Server Side Rendering）
クライアントからのリクエストがあった際に、サーバーはサーバー側でJavaScriptを実行して表示するHTMLを生成（レンダリング）します。そして生成済みのHTMLをクライアントに返し、クライアントはブラウザにそのままHTMLを表示します。

CSRと大きく異なる点は、ブラウザ側の仕事量です。CSRでは、クライアント側に「JavaScriptを実行して表示するHTMLを生成する」という仕事がありましたが、SSRではHTMLを生成する仕事はサーバー側にある為、ブラウザは返ってきたHTMLを表示するのみの仕事になります。

### メリット
- サーバー側で必要なページのみ生成し、ブラウザはそれを表示するだけなので、ページが表示されるまでの速度が早い
- ブラウザでJavaScriptを実行する必要が無い為、使用しているマシンスペックが使用しているマシンスペックに依存することが無い。それにより全てのユーザーに安定した表示速度を提供できる
- サーバーからはレンダリングが完了したHTMLを渡される為、CSRと比較してSEOで有利
- 常に最新のデータを元にページを生成することができる

### デメリット
- ページ遷移の度にサーバーへの通信が発生する

### 条件
- `fetch()`の第二引数に、`{ cache: "no-store" }`を指定している
- `useEffect`、`useState`、`onClick`などのブラウザ依存の機能を使用していない


### 実装例
```react
import React from "react";

type Product = {
  id: string;
  title: string;
};

const SsrPage = async () => {
  const res = await fetch("https://dummyjson.com/products", {
    cache: "no-store",
  });
  const data = await res.json();

  return (
    <>
      <h3>Built with SSR</h3>
      <br />
      <ul>
        {data.products.map((product: Product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default SsrPage;
```

## SSG （Static Site Generation）
アプリケーションビルド時にページを生成して静的ページとしてサーバーに保管して、リクエストがあった際にはその静的ファイルを返します。

JavaScriptを実行してレンダリングするプロセスを省くことができる為、サーバー側の仕事はリクエストがあったページを返すだけで、クライアント側の仕事はそれを表示するだけになります。

これにより、画面表示までの時間が高速になります。

### メリット
- ページ表示速度が最速

### デメリット
- 更新が入った場合に、再度手動でビルドする必要がある

### 条件
- `fetch()`の第二引数に、`{ cache: "force-cache" }`を指定している（デフォルトで設定されているオプションの為、省略可）
- `useEffect`、`useState`、`onClick`などのブラウザ依存の機能を使用していない

### 実装例
```react
import React from "react";

type Product = {
  id: string;
  title: string;
};

const SsgPage = async () => {
  const res = await fetch("https://dummyjson.com/products");
  const data = await res.json();

  return (
    <>
      <h3>Built with SSG</h3>
      <br />
      <ul>
        {data.products.map((product: Product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default SsgPage;
```

### 備考
以下のように、そもそもデータフェッチを行っていない（`fetch`関数を使用していない）場合も、デフォルトでSSGとして認識されます。
```react
import React from "react";

const SsgPage = async () => {
  return (
    <>
      <h3>Built with SSG</h3>
    </>
  );
};

export default SsgPage;
```


## ISR （Incremental Statice Regeneration）
SSRとSSGを組み合わせたレンダリング手法です。

アプリケーションビルド時にページを生成して静的ページとしてサーバーに保管して、リクエストがあった際にはその静的ファイルを返します。

ここまではSSGと同様の挙動ですが、ISRでは、特定の期間が経過したら再度ビルドを実行させることができます。

たまに更新されるような場合、手動で再ビルドを実行する必要があるSSGの弱点の解決策として誕生しました。


### メリット
- 表示速度が最速
- 自動再ビルドにより、たまに更新がある場合にも画面に反映することができる


### デメリット
- ビルドのタイミングによっては、最新の状態の画面を反映することができない

### 条件
- `fetch()`の第二引数に、`{ next: { revalidate: 10 } }`（例）を指定している
- `useEffect`、`useState`、`onClick`などのブラウザ依存の機能を使用していない

### 実装例
```react
import React from "react";

type Product = {
  id: string;
  title: string;
};

const IsrPage = async () => {
  const res = await fetch("https://dummyjson.com/products", {
    next: {
      revalidate: 30,
    },
  });
  const data = await res.json();

  return (
    <>
      <h3>Built with ISR</h3>
      <br />
      <ul>
        {data.products.map((product: Product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default IsrPage;
```


## 動作確認・ビルド

SSR・SSG・ISRの挙動は、ビルドをすることで確認することができます。

以下コマンドでビルドが実行されます。
```
npm run build
```

ビルドが正常終了すると、以下のようなメッセージが表示されます。

それぞれのページURLと、そのページがどのレンダリング方式でビルドされたかが表示されます。

ページの左側に「`○`」が表示されているものは、CSR・SSG・ISRでビルドされています。

「`λ`」が表示されているものは、SSRでビルドされています。
```bash 
Route (app)                              Size     First Load JS
┌ ○ /                                    5.29 kB        89.5 kB
├ ○ /_not-found                          885 B          85.1 kB
├ ○ /csr                                 439 B          84.6 kB
├ ○ /isr                                 144 B          84.3 kB
├ ○ /ssg                                 144 B          84.3 kB
└ λ /ssr                                 144 B          84.3 kB
+ First Load JS shared by all            84.2 kB
  ├ chunks/69-593824af3050bb4a.js        28.9 kB
  ├ chunks/fd9d1056-c50cb70e7323352b.js  53.4 kB
  └ other shared chunks (total)          1.9 kB

○  (Static)   prerendered as static content
λ  (Dynamic)  server-rendered on demand using Node.js
```

Next.jsは、ビルド時にそれぞれのページの**データフェッチを行なっている箇所 / fetch関数を使用している箇所**をチェックします。

データフェッチを実行していない`fetch`関数を使用していない）ページは、SSGとしてページを生成します。

データフェッチを実行している（`fetch`関数を使用している）ページは、`fetch`関数実行時に指定されているオプションをチェックし、それぞれ以下のようにビルドします。

- `{ cache: "force-cache" }` を指定している、または何も指定されてない 👉 **SSG**
- `{ cache: "no-store" }` を指定している 👉 **SSR**
- `{ next: { revalidate: 10 } }`（例）を指定している 👉 **ISR**