【React / Next.js】ルーティング・遷移の基礎


#  <font color="green">基本的なルーティング</font>

## ■ pages配下が対象

基本的にはNext.jsのプロジェクトを作成したときにデフォルトで配置されている、
`pagesディレクトリの配下`を参照し、ルーティングされる。

<br>
<br>

![スクリーンショット 2023-06-12 23.17.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/4ee583df-b17e-5c31-c53d-01719a506c3f.png)

<br>
<br>

サーバを起動し、ローカルホストのリンク（http://localhost:3000など） を入力しリクエストを送れば、通常以下のようなページにアクセスできる。


<br>
<br>

![スクリーンショット 2023-06-12 23.21.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/64a969ac-e247-e23c-ec3f-ea060f14fe55.png)

<br>
<br>

ここで、pages配下に一つフォルダを作成し、その中にindex.jsを作成する。（フォルダ名は任意）

<br>
<br>

**【 ディレクトリ階層 】**
>root/
　├ src/
　│　└ pages/
　│　　　└ sample/
>　│　　　　└ index.js

**【 index.js 】**
```react
export default function Home() {
  return <h1>HOME</h1>;
}
```

<br>
<br>

URLのリンクを`「 ローカルホスト / sample 」`（http://localhost:3000/sample） に変更し、リクエストを飛ばすと、以下のようなページが表示される。

<br>
<br>

![スクリーンショット 2023-06-12 23.30.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/1d957bcc-0ebc-8a02-6056-3fab9a6ab406.png)

<br>
<br>

## index.js以外のファイルを作成すると

先ほどのように、フォルダ直下に`index.js`を作成した場合は、

`「リンク / フォルダ名」`

でページにアクセスすることができる。

<br>
<br>

しかし、index.js以外の名前のファイルを作成した場合は、

`「リンク / フォルダ名 / ファイル名」`

とすることで、ページにアクセスすることができる。

<br>
<br>

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

>root/
　├ src/
　│　└ pages/
　│　　　└ sample/
>　│　　　　└ blog.js

**【 blog.js 】**
```react
export default function Blog() {
  return <h1>Blog</h1>;
}
```

<br>
<br>

リクエスト結果は以下の通り。

<br>
<br>

![スクリーンショット 2023-06-12 23.41.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/a8128150-b1f9-bcb5-ce3e-12598359042e.png)

<br>
<br>

# <font color="green">動的なルーティング（ダイナミックルーティング）</font>

## ダイナミックルーティングとは
URLの一部分を動的に変更する値にすることで、一つのファイルで複数のルーティングを可能にすること。
以下の例では、末尾の数字を動的に変更するダイナミックルーティングを作成する。

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

>root/
　├ src/
　│　└ pages/
　│　　　└ blog/
>　│　　　　└ [number].js


ダイナミックルーティングを作成するときは、`ファイル名を[]で囲い`、`[任意の名前].js`　とする必要がある。

こうすることで、`[]内の名前（今回の例でいうnumber）`がダイナミックルーティングとなり、jsファイル内で値として扱えるようになる。

<br>
<br>

**【 [number].js 】**

```react
export default function Number() {
  return <h1>[number].js</h1>;
}
```
<br>
<br>

URLのリンクを`「 ローカルホスト / blog / 1　」`にし、リクエストを飛ばす。
結果は以下の通り。

<br>
<br>

![スクリーンショット 2023-06-13 0.59.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/973b1844-0925-8d04-fef2-27bf65591867.png)

<br>
<br>

この時、末尾の`1`は好きな値に置き換えても同様のページにアクセスできる。

<br>
<br>

# <font color="green">ダイナミックルーティングの値をjs内で取得する方法</font>

取得する方法は以下の２通り。

**① getServerSidePropsという関数を使用する。**

**② React HookのuseRouterを使用する。**

<br>
<br>

# <font color="green">① getServerSidePropsを使用する</font>

getserverSidePropsメソッドから戻されたオブジェクトの`props`というプロパティに設定された値が、関数コンポーネントのプロップスとして渡ってくるという仕組み。

## 関数コンポーネントにpropsを渡してみる

<br>

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

>root/
　├ src/
　│　└ pages/
　│　　　└ blog/
>　│　　　　└ [number].js

**【 [number].js 】**

```react
export default function Number({ hello }) {
  return <h1>{hello}.js</h1>;
}
export async function getServerSideProps() {
  return {
    props: { hello: "こんにちは" },
  };
}
```

<br>
<br>

リクエストを送信。結果は以下の通り。

<br>
<br>

![スクリーンショット 2023-06-13 1.22.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/49693740-c20d-5d0d-6577-69fe2dd8dfb3.png)

<br>
<br>

## 動的にpropsの値を取得する

動的に値を取得するには、getserverSidePropsメソッドの引数である、`context`を利用する。


**【 [number].js 】**
```react
export default function Number({ hello }) {
  return <h1>{hello}.js</h1>;
}
export async function getServerSideProps(context) {
  console.log(context);

  return {
    props: { hello: "こんにちは" },
  };
}
```

<br>
<br>

consoleの出力結果を確認すると大量のログが出てくるが、注目するべきなのは以下のログ。

<br>
<br>


```react
{
  query: { number: '1' },
  resolvedUrl: '/07_router/blog/1',
  params: { number: '1' },
  locales: undefined,
  locale: undefined,
  defaultLocale: undefined
}
```

<br>
<br>

この`query`というところに、自分が入力した任意のパスの値が入っていることが分かる。

つまり、この`context`内の`query.number`にアクセスすれば、リクエスト時に入力した任意の値を動的に取得することが可能になる。

早速実践。

<br>
<br>

```react
export default function Number({ query }) {
  return <h1>{query.number}.js</h1>;
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}

```

<br>
<br>

結果は以下の通り。

<br>
<br>

![スクリーンショット 2023-06-13 2.00.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/55485e10-8b8f-b9f1-8e54-eee0cb7a5f91.png)

<br>
<br>

先ほどのログでは、

<br>
<br>

```react
query: { number: '1' },
```

<br>
<br>

となっていたが、オブジェクトのプロパティ名はダイナミックルーティングの[]内に記載した名前によって変わる。

<br>
<br>

# <font color="green">② React HookのuseRouterを使用する</font>

先ほどのgetServerSidePropsとは違うメソッドになるが、利用の仕方はほぼ同じ。

**【 [number.js] 】**

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

export default function Number({ query }) {
  const router = useRouter();

  console.log(router);

  return <h1>{router.query.number}.js</h1>;
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```

<br>
<br>

結果とログは以下の通り。

<br>
<br>

![スクリーンショット 2023-06-13 2.25.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/561ad9fc-e4cd-b9c8-b7c8-99c64178540d.png)

<br>
<br>

**ログ**

```react
ServerRouter {
  route: '/07_router/blog/[number]',
  pathname: '/07_router/blog/[number]',
  query: { number: '1' },
  asPath: '/07_router/blog/1',
  isFallback: false,
  basePath: '',
  locale: undefined,
  locales: undefined,
  defaultLocale: undefined,
  isReady: true,
  domainLocales: undefined,
  isPreview: false,
  isLocaleDomain: false
}
```

<br>
<br>

## useRouterからクエリパラーメータを取得

**【クエリパラメータ】**
URLのリンクの?以降のkeyと値のセットのこと。

**例）http://localhost:8080/sample/1<font color="red">?key=value</font>**

**【 リクエストのリンク 】**

![スクリーンショット 2023-06-14 0.28.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/5b4a20c8-c708-1e67-d654-d5c290257d7b.png)

<br>
<br>

**【 ログ 】**

![スクリーンショット 2023-06-14 0.27.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/8d0e6fc8-2db5-a0cd-dac6-41d78cd88d09.png)

<br>
<br>

# useRouterを利用して画面遷移を行う

useRouterの`push`メソッドを利用することで、画面遷移を行うことが可能。

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

export default function Number({ query }) {
  const router = useRouter();

  console.log(router);

  const clickHandler = () => {
    // トップページに戻る
    router.push("/");
  };
  return (
    <>
      <h1>{router.query.number}.js</h1>
      <button onClick={clickHandler}>アクションによる画面遷移</button>
    </>
  );
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```

<br>
<br>

**【 画面 】**

![スクリーンショット 2023-06-14 0.37.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/655454c5-c45b-709a-5aff-ed7c1bbdfeec.png)


<br>
<br>

**【 ボタンクリック後 】**

![スクリーンショット 2023-06-14 0.39.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/83c804dc-6732-222c-53e4-5a5282baac2e.png)

<br>

## 補足

`push`メソッドは、第二引数にダミーのURLを設定することで、第一引数のリンクに遷移しつつ、画面に表示されるリンクを第二引数にすることができる。


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

export default function Number({ query }) {
  const router = useRouter();

  console.log(router);

  const clickHandler = () => {
    // トップページに戻る
    router.push("/", "dummy-url");
  };
  return (
    <>
      <h1>{router.query.number}.js</h1>
      <button onClick={clickHandler}>アクションによる画面遷移</button>
    </>
  );
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```

<br>
<br>

**【 画面（遷移前） 】**

![スクリーンショット 2023-06-14 0.43.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/cf7f8074-8cab-a31b-cef8-1b3f2830f2ff.png)

<br>
<br>

**【 遷移後 】**

![スクリーンショット 2023-06-14 0.45.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/a2d9fcee-0aad-7b49-8a93-0fc18927bc49.png)

<br>
<br>

## 遷移 ＋ 履歴を上書き

`push`メソッドと同様に画面遷移をすることに加え、履歴を上書きするメソッドとして、`replace`メソッドが存在する。

<br>
<br>

## 解説（pushメソッド）

まず`push`メソッドの場合の挙動確認。最初に以下のリンクにアクセス。

<br>
<br>

![スクリーンショット 2023-06-14 1.08.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/f353b10f-6515-ec7b-6a6b-430768aa8437.png)

<br>
<br>　

ここから「アクションによる画面遷移」ボタンをクリックし、トップページに遷移する。

<br>
<br>

![スクリーンショット 2023-06-14 0.45.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/c49fa3af-776a-93a4-b674-a2153715e583.png)

このときにブラウザバック（左上の矢印の戻るボタン）ボタンをクリックすると、
先ほどの一番最初にリクエストで送信した画面に戻る。

<br>
<br>

![スクリーンショット 2023-06-14 1.08.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/f353b10f-6515-ec7b-6a6b-430768aa8437.png)

<br>
<br>

という挙動になる。`push`メソッドはこれまでの履歴に`１つ履歴を追加する`というメソッドという認識が正しい。

**googleトップページ　→　blog/1のページ<font color="red">　→　dummy-url（履歴を追加）</font>**

## 解説（replaceメソッド）

次にreplaceメソッドの挙動を確認。ソースは以下の通り。

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

export default function Number({ query }) {
  const router = useRouter();

  console.log(router);

  const clickHandler = () => {
    // トップページに戻る
    router.push("/", "dummy-url");
  };
  return (
    <>
      <h1>{router.query.number}.js</h1>
      <button onClick={clickHandler}>アクションによる画面遷移</button>
    </>
  );
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```

<br>
<br>

この状態で先ほどと同じ動作確認を実行。

<br>
<br>

![スクリーンショット 2023-06-14 1.08.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/f353b10f-6515-ec7b-6a6b-430768aa8437.png)

<br>
<br>

ボタンを押下し、画面遷移。

<br>
<br>

![スクリーンショット 2023-06-14 0.45.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/c49fa3af-776a-93a4-b674-a2153715e583.png)

<br>
<br>

そしてこの状態でブラウザバックボタンを押すと、リクエストを飛ばす前のgoogleのトップページへ遷移する。

<br>
<br>

![スクリーンショット 2023-06-14 1.15.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/d490b1ce-c78e-1ca0-a3c8-c90f8051d0a4.png)

<br>
<br>

これが`replace`メソッドの上書きである。

**googleトップページ　→　~~blog/1のページ~~<font color="red">　→　dummy-url（履歴を上書き）**


# <font color="green">フォルダもダイナミックルーティングできる</font>


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

>root/
　├ src/
　│　└ pages/
　│　　　└ [name]/
　│　　　　└ settings.js
　│　　　└ blog/
>　│　　　　└ [number].js

**【 settings.js 】**

```react
export default function Settings() {
  return <h1>[name]/settings.js</h1>;
}
```

URLのパスを、`「 ローカルホスト / A / settings　」`にし、リクエストを飛ばす。
結果は以下の通り。

<br>
<br>

![スクリーンショット 2023-06-13 0.28.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/03f93138-8bab-a4cb-5089-abfb029e20f2.png)

もちろん、 / A / のところはどんな名前でもアクセス可能。

<br>
<br>

## ダイナミック vs 固定のパス

>root/
　├ src/
　│　└ pages/
　│　　　└ [name]/
　│　　　　└ settings.js
　│　　　└ blog/
>　│　　　　└ [number].js

上記のようなディレクトリ階層の時、
`ローカルホスト / blog / settings`
というリンクでリクエストを飛ばすとどうなるのかを検討する。

`/blog/`はblogフォルダはもちろんのこと、[name]フォルダも該当する。

`/settings/`も同様に、どちらのファイルにも該当する。

結果は以下の通り。


<br>
<br>

![スクリーンショット 2023-06-13 0.37.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/1b26317b-6e68-0500-b3f2-aa1c80b62ff1.png)

### 結果は固定パスが優先

`ダイナミックルーティングを行なっている同階層に同じ固定のパスがあり、そちらに一致する場合は、固定パスの方が優先される`ということになる。

<br>
<br>

## ダイナミックvsダイナミック

それでは、同階層にダイナミックルーティングが２つ以上あったらどうなるのか。

結論、これは`エラー`になる。
システム上、<font color="red">同階層に設定できるダイナミックルーティングは１つまで</font>というルールがあるので、同階層に複数のダイナミックルーティングの設定はできない。

<br>
<br>

# <font color="green">Linkコンポーネントを利用した画面遷移</font>

Linkコンポーネントを使用して、画面遷移を行う方法も紹介。

<br>
<br>

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

export default function Number({ query }) {

  return (
    <>
      <Link href="../set/settings">
        <a>[name]/settingsに遷移</a>
      </Link>
    </>
  );
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```

<br>
<br>

**【 画面 】**

![スクリーンショット 2023-06-14 1.54.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/af2ad26d-0aab-8c3d-b3e3-62d938c26dee.png)

<br>
<br>

**【 リンク押下 】**

![スクリーンショット 2023-06-14 1.55.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/337aea4e-dc96-46c8-890b-6ca0641a695d.png)

<br>
<br>

## メリット
Linkコンポーネントを使用した遷移のメリットとしては、遷移するときに画面のリロードがされないので、画面遷移の高速化が期待できる。

試しにLinkコンポーネントと通常のaタグで遷移した場合の挙動確認を行う。

<br>
<br>

## Linkコンポーネントの場合

背景を水色にし、Linkコンポーネント側のリンクで画面遷移をすると、

![スクリーンショット 2023-06-14 2.00.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/b33ddf5c-29ae-a361-b88d-a1605b374583.png)

<br>
<br>

![スクリーンショット 2023-06-14 2.02.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/168733f7-c7c7-79dc-3e5b-b445c8a7e022.png)

<br>
<br>

背景色が変わらないまま画面遷移ができる。

<br>
<br>


## アンカータグの場合
同様の条件でaタグのリンクをクリックすると、

<br>
<br>

![スクリーンショット 2023-06-14 2.03.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/be35c455-2eab-1b42-68de-82b5d8a68636.png)

<br>
<br>

画面がリロードされるため、ディベロッパーツールで設定したスタイルが初期化され、背景色が白色に戻っていることがわかる。

<br>
<br>

![スクリーンショット 2023-06-14 2.04.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/08ed3ec7-b844-dd27-8182-86feefabe9ff.png)

<br>
<br>

## <font color="red">アンカータグの画面遷移による弊害</font>

アンカータグの繊維による弊害は、Linkコンポーネントと比較して低速というだけではない。

リロードを行うと、`state（状態）で管理している値`が初期化されてしまう。

以下の例は、ある画面で保持した値を別画面に共有して表示する例である。

![スクリーンショット 2023-06-15 2.25.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/41125d9e-e3ed-1f20-9d6c-1c5d60bca296.png)

<br>
<br>

「リストページへ」ボタンを押下し、赤枠のデータを別画面に共有する。

<br>
<br>

![スクリーンショット 2023-06-15 2.27.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/32d2358e-719c-b79c-c7d6-b06b80761a3d.png)

<br>
<br>

値が保持されていることが確認できる。
しかし、これをアンカータグで遷移すると、

<br>
<br>

![スクリーンショット 2023-06-15 2.29.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/d2d38280-695d-0b10-93b9-300b2d392e93.png)

<br>
<br>

このように、リロードされたことによりstateの値が初期化されるので、遷移したときには何も表示されないという現象が起きる。

<br>
<br>


## その他リンクの設定も可能

useRouterの`push`メソッドや`replace`メソッドと同様、画面表示用のダミーURLを設定することが可能。

Linkコンポーネントに`as属性`を設定することで設定ができる。

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

export default function Number({ query }) {
  const router = useRouter();

  console.log(router);

  const clickHandler = () => {
    // トップページに戻る
    router.replace("/", "dummy-url");
  };
  return (
    <>
      <Link href="/" as="dummy-url">
        <a>トップ</a>
      </Link>
    </>
  );
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```

クエリパラメータの設定や、リンクをオブジェクト形式で記載することも可能。

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

export default function Number({ query }) {
  const router = useRouter();

  console.log(router);

  const clickHandler = () => {
    // トップページに戻る
    router.replace("/", "dummy-url");
  };
  return (
    <>
      <Link
        href={{
          // パスのオブジェクト設定
          pathname: "/",
          // クエリパラメータの設定
          query: { key: "value" },
        }}
        as="dummy-url"
      >
        <a>トップ</a>
      </Link>
    </>
  );
}
export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```
![スクリーンショット 2023-06-14 2.31.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/c49e149e-c725-dbda-f744-ca2c1abdaf00.png)