【Next.js】SSRの挙動について理解する(SSRの仕組み、CSRとの違い、getServerSideProps関数)

この記事は個人学習の備忘録です。

Udemyの講座[【2023年最新】React(v18)完全入門ガイド｜Hooks、Next.js、Redux、TypeScript(CodeMafia)](https://www.udemy.com/course/react-complete-guide/)

上記を参考・引用しながらNext.jsの基本のレンダリングについて個人的に学んだことのアウトプット記事です。

前回の記事ではCSR/SSR/SSG/ISRそれぞれの基本のレンダリングについてのアウトプットの記事を書きました。

https://qiita.com/marl0401/items/d5f0dd91e06c8b52a49d

今回のアウトプットではSSRに絞って理解したことをまとめました。

## SSRの挙動について理解する
SSRの観点からどのようなタイミングで下記の関数コンポーネントが実行されるのかを理解します。

```/src/pages/010_SSR/index.js
export default function SSR() {
  console.log("hello");
}
```

「010_SSR」フォルダのindex.js内でconsole.logを実行し、ブラウザではこのURLに遷移した状態で更新ボタンを押します。

VSCodeのターミナル上に出ているのがNode.jsの実行ログになります。
![スクリーンショット 2023-07-09 14.02.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/8efe89f8-d36f-5af4-e7f6-ecf3aa2eb7fb.png)
Node.jsの実行ログにも、ブラウザ上のコンソールにも両方"hello"と出ています。
![スクリーンショット 2023-07-09 13.54.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/0571882f-e7eb-af2e-ed91-0955d987f0a3.png)
Next.jsの場合では、Node.js上とブラウザ上、どちらもこの関数コンポーネントが実行されていることが分かります。

### 変数の場合

コードを追加します。
```/src/pages/010_SSR/index.js
export default function SSR(){
  console.log("hello")
  // 追加
  const val = 0
  return <h3>{val}</h3>
}
```
![スクリーンショット 2023-07-09 14.12.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/6c8bbfe3-59bd-d02d-f27a-701162b74f66.png)
上記の場合でもブラウザ上、Node.js上でも"hello"と出てきますが、この０という値はブラウザ上、Node.js上**どちらで保管された値なのか**をみていきます。

右クリックで検証ツールを開き、「ページのソースを表示」をクリックします。

![スクリーンショット 2023-07-09 14.13.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/42f9bf3b-3c21-e01d-d092-278159b3d541.png)

これによってHTMLが表示されます。このHTMLは**サーバーから受け取ったHTML**になります。
![スクリーンショット 2023-07-09 14.15.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/acf97141-6157-f6d3-ed19-bd3fa36d0953.png)
「h3」タグで検索すると、`<h3>0</h3>`がHTMLの中に存在しています。
![スクリーンショット 2023-07-09 14.18.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/937e2e4e-dfe8-928f-f032-384f2bf154ae.png)
つまり、**Node.js上でvalの値が０に置換されて、ブラウザ側に返却されている**ということになります。valの値の0というのはNode.js上で保管されていました。これがSSRの機能です。

SSRの場合には、リクエストが送信される(=ブラウザで指定されたURL)と、Node.jsのサーバーに対してリクエストが送信されて、そのリクエストを受け取ると関数が実行されます。
関数が実行されると、変数の部分が代入されて、jsxが返されるので、このjsxがReactの要素となってHTMLに保管されてからブラウザにレスポンスとして返却されます。HTMLがすでに出来上がった状態でブラウザに返却されることになります。

### stateの場合
stateの場合はどうなるのかみてみます。

```/src/pages/010_SSR/index.js
import { useState } from "react";

export default function SSR() {
  console.log("hello");
  const [state, setState] = useState("bye");
  return <h3>{state}</h3>;
}
```
この時も、値が代入された状態でHTMLが返却されていることが分かります。
![スクリーンショット 2023-07-09 14.48.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/e97d5a09-a5a5-4ce3-8e3d-56163ec445e0.png)
![スクリーンショット 2023-07-09 14.48.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/29e2be21-9ad2-bf2e-2f2a-96b2181c4bd6.png)

このようにNext.jsの場合ではstateの場合でも値が代入された状態でHTMLが返ります。
Reactは内部に仮想DOMを保持しているため、擬似的にNode.js上でDOMツリーのようなものを作成し、それを元にしてHTMLを作成できるため、Node.js上で値が代入された状態で返却することが可能になっています。

### 注意点

関数はNode.js上でも実行されるので、**Node.jsで存在しないwindowオブジェクトは使用することができません**。
なので、例えばブラウザでしか保持していないwindow.documentにはアクセスすることはできません。

もし、windowオブジェクトにアクセスするような処理を書きたい場合には、useEffectの中で行います。
useEffectの中に書かれた処理は、必ずブラウザ上で処理されることになります。なので関数コンポーネントのトップレベルには書かずに、useEffectのコールバック関数内に記述することでエラーが出ずwindowオブジェクトにアクセスすることが可能になります。

```/src/pages/010_SSR/index.js
import { useState, useEffect } from "react";

export default function SSR() {
  console.log("hello");
  // 追加
  useEffect(() => {
    window.localStorage.setItem("key", "value");
  }, []);
  const [state, setState] = useState("bye");
  return <h3>{state}</h3>;
}
```
この場合ではエラーが出ず実行されますが、`window.localStorage.setItem("key","value")`をuseEffectから外に出すとエラーが出ます。
```/src/pages/010_SSR/index.js
import { useState, useEffect } from "react";

export default function SSR() {
  console.log("hello");
  // 外に出す
  window.localStorage.setItem("key", "value");
  useEffect(() => {
    window.localStorage.setItem("key", "value");
  }, []);
  const [state, setState] = useState("bye");
  return <h3>{state}</h3>;
}
```
![スクリーンショット 2023-07-09 15.23.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/ae1bf710-169b-6828-76c4-21d482cd58fa.png)
![スクリーンショット 2023-07-09 15.23.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/276ec759-42a9-b4b4-5d2a-9ba68fac497e.png)

window.localStorageは**副作用(※)の処理**ということになってきますので、useEffectの処理に含めることになります。

※副作用・・・描画の一部ではない処理のこと
[useEffectについての記事はこちら](https://qiita.com/marl0401/items/b05a73d27ceab2483cc1)

### 画面遷移する場合

トップページに戻り、一度画面を更新してから`/010_SSR`のURLに移動します。
その時、ブラウザのconsole.logには"hello"と追加されましたが、Node.js上のログには何も追加されません。
![ezgif.com-video-to-gif (7).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/18e222c1-6bdd-ca7d-0d94-8db76741c5ae.gif)
![スクリーンショット 2023-07-09 15.34.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/e730cf61-d5f2-5da0-76d2-9e1b3a17dfdc.png)


一方で、最初から`/010_SSR`で更新すると、ブラウザ上もNode.js上でも"hello"という文字列が追加されます。
![スクリーンショット 2023-07-09 15.38.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/e2bd022a-abce-97a0-b64e-78b47a124247.png)
![スクリーンショット 2023-07-09 15.38.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/f0c44a48-b689-b390-d153-7b73b81b149c.png)
これは、画面が一番最初に表示される時、その時はNode.js上の関数コンポーネントが実行されるのですが、他のページから移ってきた時は関数コンポーネントが実行されません。
あくまで、他のページから移った際はブラウザ上で関数コンポーネントが実行されることになります。

このように、ブラウザ上でレンダリングの処理が実行されることを**CSR**と呼びます。初期表示の時は**SSR**で、画面遷移を行う時は**CSR**になってきます。

## getServerSideProps関数について学ぶ

SSRの関数の時に実行される`getServerSideProps`の関数の使い方について学びます。

```/src/pages/010_SSR/index.js
import { useState, useEffect } from "react";

export default function SSR() {
  console.log("hello");
  useEffect(() => {
    window.localStorage.setItem("key", "value");
  }, []);
  const [state, setState] = useState("bye");
  return <h3>{state}</h3>;
}

// 追加
export async function getServerSideProps(context){

}
```
この関数はNode.js上で実行される関数になりますが、この関数の`context`の引数に対して、リクエストとレスポンスの情報が乗ってきます。
returnの値に関しては、サーバーで実行するような値をこちらで実行することができます。

SSR関数コンポーネントの引数にpropsとして渡す値をここで設定することができます。
例えば,propsにmessageというキーを用意し、メッセージの値をSSRの関数コンポーネントの引数に渡すことができます。
```/src/pages/010_SSR/index.js
import { useState, useEffect } from "react";

// 引数にmessageを追加
export default function SSR({ message }) {
  console.log("hello");
  // 追加
  console.log(message)

  useEffect(() => {
    window.localStorage.setItem("key", "value");
  }, []);
  const [state, setState] = useState("bye");
  return <h3>{state}</h3>;
}

export async function getServerSideProps(context) {
  // 追加
  return {
    props: { message: "From Server Side" },
  };
}
```
このメッセージがコンソールに出力されます。
![スクリーンショット 2023-07-09 16.06.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/b4726599-4985-4db4-ba12-015c16a22659.png)
![スクリーンショット 2023-07-09 16.06.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/558863b5-3cf9-894a-61d0-b9d0afa6d4de.png)

リダイレクトの処理も可能です。`010_SSR`ページに飛んできたものをTOPページに対して転送することになります。

```/src/pages/010_SSR/index.js
export async function getServerSideProps(context) {
  return {
    // 追記
    redirect: {
      destination: "/",
    },
    props: { message: "From Server Side" },
  };
}
```
![ezgif.com-video-to-gif (8).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/60de7fd0-bbde-c690-7057-0453a98619c7.gif)

この時にパーマネントというオプションを渡すこともできます。パーマネントはリダイレクト処理を一時的に行うか、恒久的に行うかという設定で、trueにすると恒久的にリダイレクト処理を行います。
```/src/pages/010_SSR/index.js
export async function getServerSideProps(context) {
  return {
    redirect: {
      destination: "/",
      // 追記
      permanent: true,
    },
    props: { message: "From Server Side" },
  };
}
```
contextに関しては、リクエストやレスポンス、またはクエリ、パラメーターなどの通信の情報が乗ってきます。
例えばリクエストに乗ってきたcookieを取り出すこともできます。(cookieの値は仮で設定)

```/src/pages/010_SSR/index.js
import { useState, useEffect } from "react";

export default function SSR({ message }) {
  console.log("hello");

  console.log(message);

  useEffect(() => {
    window.localStorage.setItem("key", "value");
    // cookieの値を設定
    document.cookie = "val=0; path=/;";

  }, []);
  const [state, setState] = useState("bye");
  return <h3>{state}</h3>;
}

export async function getServerSideProps(context) {

  // 追加
  const { cookie } = context.req.header;
  console.log(cookie);

  return {
    // redirect: {
    //   destination: "/",
    //   permanent: true,
    // },
    props: { message: "From Server Side" },
  };
}
```
上記のようにcookieの値を設定し、パスをルートパスに指定し、画面更新すると検証ツールのApplicationのcookieの値が設定されてることを確認できました。
![スクリーンショット 2023-07-09 23.17.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/2b5eb4a7-77e2-4bb2-2e10-62d68c6dba9c.png)
Node.js上でもval=0が設定されていることが確認できます。
![スクリーンショット 2023-07-09 23.21.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/bf8db9b8-84f9-d05a-be21-cacde03149f0.png)
このように、リクエストやレスポンスもgetServerSideProps関数のcontextから取得できることを確認できます。

覚えておくべきこととして、getServerSideProps関数はあくまでNode.js上で実行される関数です。これはブラウザ上では実行されません。

SSRとCSRがうまく共存する形でNext.jsのアプリケーションは動作するようになっています。これが基本的なgetServerSideProps関数の特徴です。

## 参考

https://www.udemy.com/course/react-complete-guide/