Next.jsの基本的な使い方(個人学習の備忘録)

###

この記事はNext.jsの個人学習のアウトプットです。

Udemyの講座[【2023年最新】React(v18)完全入門ガイド｜Hooks、Next.js、Redux、TypeScript(CodeMafia)](
https://www.udemy.com/course/react-complete-guide/)から引用・参照・コピーペーストしています。


## 1.Next.jsとは

- React開発のためのフレームワーク
- 高度なアプリケーションを作成するために、様々な機能を提供
┗ルーティングやサーバー側でのレンダリングなど
- Vercelが提供している

### Reactとの違い

- React 
UIを構築するための機能を提供する**ライブラリ**

- Next.js
react開発のための機能を提供する**フレームワーク**


### Next.jsの主な機能


- 複数のレンダリング方法(SSR、SG、ISG)
- ファイルベースルーティング(ダイナミックルート)
- APIの作成(API Routes)
- デベロッパーに優しい開発環境(ゼロコンフィグ)



## 2.ルーティング

Next.jsは、pagesフォルダ内のファイル（またはディレクトリ）構成に対応してルーティングを行います。

```src/pages/index.js
export default function Home() {
  return (
    <h1>Home</h1>
  )
}

// http://localhost:3000/
```

![スクリーンショット 2023-06-24 15.49.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/f292f905-3caf-23c2-cc68-c6615649ec55.png)

ブラウザでlocalhost:3000にアクセスすると、作成したページが表示されます。

実行結果
![スクリーンショット 2023-06-24 15.55.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/1784dd4d-b6d1-ebe0-2216-d8c9201e3f4f.png)


Next.jsはデフォルトエクスポートの関数がそのまま関数として表示されることになるので、例えば`「blog-router」`というフォルダを作成し、index.js内で同じように関数コンポーネントをデフォルトエクスポートすると「blog-router」というパスに対してページが表示されます。

```src/pages/blog-router/index.js
export default function Blog() {
  return <h1>Blog Page</h1>
}

// http://localhost:3000/blog-router
```

![スクリーンショット 2023-06-24 15.57.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/0f1d35fa-2b8a-2efb-ff0a-0bf013ba0ae3.png)


実行結果
![スクリーンショット 2023-06-24 15.56.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/d470418b-193e-4514-2e4b-11c8b67aebe7.png)

index.js以外のファイルで作成した場合は、パスはjavascriptのファイル名まで続けて指定する必要があります。

```src/pages/blog-router/first.js
export default function First() {
  return <h1>Blog First Page</h1>
}

// http://localhost:3000/blog-router/first
```

![スクリーンショット 2023-06-24 16.00.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/8738ef5d-1973-c02f-e300-a309e4887cd9.png)

実行結果

![スクリーンショット 2023-06-24 16.01.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/d1fb0a1e-d5d4-d5d5-edb1-7c95dd935fdd.png)


### 動的なルーティング

ダイナミックルーティングを用いると、URLの一部分を動的な値に変換することができるので、jsファイルのひとつだけ作成すればいいことになります。

123と数値が動的に変わっていくようなURLに対応するダイナミックルーティング

```src/pages/blog-router/[number].js
export default function Number() {
  return <h1>[number] Page</h1>
}

// http://localhost:3000/blog-router/1
// http://localhost:3000/blog-router/2
// http://localhost:3000/blog-router/3
```

![スクリーンショット 2023-06-24 16.11.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/89e83933-9625-eeba-b3fc-29bd00379ea3.png)


実行結果
![ezgif.com-video-to-gif (7).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/14cf4298-ff03-8ad8-d239-7c269fd44fb9.gif)

フォルダにダイナミックルーティングを用いることも可能です。フォルダ名に[name]にすると、動的に変更することが可能になります。

```src/pages/blog-router/[name]/setting.js
export default function Setting() {
  return <h1>[name] Setting</h1>
}

// http://localhost:3000/blog-router/a/setting
// http://localhost:3000/blog-router/b/setting
// http://localhost:3000/blog-router/c/setting
```

![スクリーンショット 2023-06-24 16.34.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/2bd2afd2-462e-a11a-a330-12e5bfaa358f.png)


実行結果
![ezgif.com-video-to-gif (8).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/0ee699ca-a5d4-d4e2-9c49-e0811d470394.gif)


:::note warn
注意

同階層に２つのダイナミックルーティングを作成することはできません。

![スクリーンショット 2023-06-24 16.25.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/15094290-5deb-d84c-cce0-b037f1b6778b.png)

:::

## 3.指定されたパスの値をjsで取得する

動的に変化するパスをjsで取得するにはどうすれば良いでしょうか。
2つの方法があります。

### 方法①`getServerSideProps`という関数を使用する

propsというプロパティに設定された値が、この関数コンポーネントのpropsとして渡ってきます。

```src/pages/blog-router/[name]/setting.js

// propsにhelloを渡す
export default function Setting({ hello }) {
  return <h1>{hello}</h1>
}

export async function getServerSideProps(){
  return { 
    props: { hello: "こんにちは" } 
  }
}
```
![スクリーンショット 2023-06-24 17.15.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/91de153a-b0b5-e20b-279b-3e7bddf6d929.png)

実行結果
![スクリーンショット 2023-06-24 17.13.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/c179c9e7-5916-d7e1-25a3-d84e2037fed0.png)

#### queryを用いて、動的なパス部分を取得

今回の[name]の動的なパスに関しては、引数でcontextが渡ってきて、その中のqueryというオブジェクトの中に格納されています。
queryを渡してあげれば、画面上をどのようなパスで渡ってきたのか表示することができます。

```src/pages/blog-router/[name]/setting.js
export default function Setting({ query }) {
  return <h1>{query.name}</h1>
}

export async function getServerSideProps({ query }){
  return { 
    props: { query } 
  }
}
```

![スクリーンショット 2023-06-24 17.19.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/639de884-f346-7dfd-e713-121463d234a2.png)

![ezgif.com-video-to-gif (9).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/289475a9-890b-6c61-4757-416ddb7cfffc.gif)


### 方法② Next.jsが提供するRouterというライブラリからReactフックを読み込む


```src/pages/blog-router/[name]/setting.js
import { useRouter } from "next/router";

export default function Setting({ query }) {
  const router = useRouter();
  return <h1>routerから取得:{router.query.name}</h1>;
}

export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```

![スクリーンショット 2023-06-24 17.31.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/8aeb06e6-8dad-4b83-9281-67b3e1ce2289.png)

![ezgif.com-video-to-gif (10).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/ea85b5f0-c175-7e0b-ab2a-4cf6498d62d0.gif)

これら２つの方法で、動的なルートの部分を取得することができます。

## 4.useRouter を使って画面遷移を行う

buttonタグにclickイベントを定義し、routerのpushメソッドを使用します。この時スラッシュとすれば、rootのパスに一致することになるので、TOPページに戻ることになります。

```src/pages/blog-router/[name]/setting.js
import { useRouter } from "next/router";

export default function Setting({ query }) {
  const router = useRouter();
  const clickHandler = () => {
    router.push('/')
  }
  return (
    <>
      <h1>routerから取得:{router.query.name}</h1>
      <button onClick={clickHandler}>アクションによる画面遷移</button>
    </>
  );
}

export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}
```


![スクリーンショット 2023-06-24 21.50.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/90066c97-bfee-cb91-77fa-4bec0b070b4d.png)


実行結果
![ezgif.com-video-to-gif (11).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/ce98eba2-bdd1-1600-9a02-a5914303e125.gif)

第二引数に対して例えば`/dummy-url`と置くと、表示されるページはTOPページだが、URLのパスは`http://localhost:3000/dummy-url` このダミーURLになる。

```src/pages/blog-router/[name]/setting.js
import { useRouter } from "next/router";

export default function Setting({ query }) {
  const router = useRouter();
  const clickHandler = () => {
    router.push('/','/dummy-url')
  }
  return (
    <>
      <h1>routerから取得:{router.query.name}</h1>
      <button onClick={clickHandler}>アクションによる画面遷移</button>
    </>
  );
}

// http://localhost:3000/dummy-url

export async function getServerSideProps({ query }) {
  return {
    props: { query },
  };
}

```

実行結果
![スクリーンショット 2023-06-24 21.56.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/66414bdf-d804-8e0e-dc87-cb201094ff11.png)

:::note warn
補足
pushを`replace`に変換すると、現在表示されている履歴が上書きされる。
そのため、ブラウザバックを押すと先ほどの前に開いていた画面が表示される。
```
  const clickHandler = () => {
    router.replace('/','/dummy-url')
  }
```
![ezgif.com-video-to-gif (12).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/863e17c3-9de1-8e01-d53b-e3933cd90731.gif)

:::


## 5.Linkを使って画面遷移を行う

Reactの画面でリンクを置いて画面遷移を行う場合はリンクコンポーネントを使用します。

```src/pages/index.js
import Link from "next/link"

export default function Home() {
  return (
    <>
      <h1>Home</h1>
      <Link href="/blog-router">
        <a>blog-router</a>
      </Link>
    </>
  );
}

// http://localhost:3000/
```

![スクリーンショット 2023-06-25 1.36.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/b03143f9-2754-d628-73e7-6dae5222cc0e.png)

実行結果
![ezgif.com-video-to-gif (13).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/316a72a2-14a6-a47e-1751-e733d2d47e6e.gif)

:::note warn
補足
`<a href="/blog-router">blog-router</a>`
これだと画面が更新され、リロードが走る。

Linkタグで囲むと、画面遷移が発生しない。
:::

routerと一緒で`as`を利用して仮想のURLを画面上で表示することも可能です。
例えば`as="/dummy-url"`とすると、画面上では`/dummy-url`に飛ぶが、リンクを押して遷移する先はこれまで通り`blog-router`になる。

```src/pages/index.js
import Link from "next/link"

export default function Home() {
  return (
    <>
      <h1>Home</h1>
      <Link href="/blog-router" as="/blog-dummy">
        <a>blog-router</a>
      </Link>
    </>
  );
}

// http://localhost:3000/
```

hrefはオブジェクト形式で書くことも可能です。オブジェクトで書く場合にはpathnameのところに遷移させたいURLを記載し、queryも指定することで、URLの末尾にクエリパラメーターをつけることができます。

```src/pages/index.js
import Link from "next/link"

export default function Home() {
  return (
    <>
      <h1>Home</h1>
      <Link href={{ pathname: "/blog-router", query: { key: "value" } }}>
        <a>blog-router</a>
      </Link>
    </>
  );
}

// http://localhost:3000/
```
![スクリーンショット 2023-06-25 1.51.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/062fdfb1-d84e-aeda-02db-9afc91dc18e7.png)


実行結果
![ezgif.com-video-to-gif (15).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/13357e73-f24a-c7d6-28ba-9b58ee29a817.gif)


## 6.ページをまたぐ状態の保持の方法

下記のコードを実行します。
ボタンがクリックされると、routerのpushメソッドが呼ばれて画面遷移が発生します。その時のパスはgoToStep関数の第二引数で設定した`/personal`で表示されますが、実体としては`/multipage?step=${step}`このパスになります。

```src/pages/multipage/index.js
import { useRouter } from "next/router";

export default function Multipage() {
  const router = useRouter();

  // URLで渡されたパラメーターを取得
  // stepがついてこなかった場合(nullまたはundefind)は初期値を0にする
  const step = router.query.step ?? 0;

  // 渡ってきたステップの値をパラメーターのステップの値として設定
  const goToStep = (_step, asPath) => {
    router.push(`/multipage?step=${_step}`, asPath);
  };
  
  return (
    <div>
      {step == 0 && (
        <>
          <h3>Step:{step}</h3>
          {/* ボタンがクリックされたときに次のステップに遷移する */}
          <button onClick={() => goToStep(1, "/personal")}>Next Step</button>
        </>
      )}
      {step == 1 && (
        <>
          <h3>Step:{step}</h3>
          <button onClick={() => goToStep(2, "/comfirm")}>Next Step</button>
        </>
      )}
      {step == 2 && (
        <>
          <h3>Step:{step}</h3>
          {/* 最後のボタンを押した時はstep==0のページの戻る */}
          <button onClick={() => goToStep(0, "/multipage")}>Next Step</button>
        </>
      )}
    </div>
  );
}
```

![スクリーンショット 2023-06-25 2.32.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/dcc26a40-489d-3aed-48d3-03c2e812e161.png)

実行結果

![ezgif.com-video-to-gif (16).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/0782ac47-543b-d450-f277-633ec80cd9f6.gif)

step:0 http://localhost:3000/multipage
step:1 http://localhost:3000/personal
step:2 http://localhost:3000/comfirm

ボタンをクリックすると遷移できるようになりましたが、上記はあくまでダミーのパスであり、単体でアクセスすることはできません。

![スクリーンショット 2023-06-25 2.40.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/af87cfdd-5dea-6a27-6775-cac72d539081.png)


このような時に、このパスを直接指定された時に動かすために、`リライト`という仕組みを用いることがあります。
next.config.jsに`rewrites`という設定を追加します。
今回の場合では、`/personal`というパスにリクエストが来た時に`destination`に記載されたパスのコンポーネントを動かすことができます。

```next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  async rewrites() {
    return [
      {
        source: "/personal",
        destination: "/multipage?step=1"
      },
      {
        source: "/comfirm",
        destination: "/multipage?step=2"
      }
    ]
  }
}

module.exports = nextConfig
```

サーバを一度再起動すると、直接アクセスすることが可能になりました。

![ezgif.com-video-to-gif (17).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/829b8839-1329-1526-19bc-b61eb0c7a838.gif)

## まとめ

### プロジェクト構成

- /pages
ファイルまでのパスがそのままページになる
- styles
グローバルに適用されるスタイルを配置
- /pages/_app.js
ページ遷移時に必ず呼ばれる処理を記述
- next.config.js
Next.jsの設定を記載

### ルーティング

- pages配下からexportされたコンポーネントを1ページとしてルーティングする
- [id]は動的なパスとして認識される
- getServerSideProps
queryでダイナミックルートを取得する

### ページ遷移

- useRouter
ページ遷移を行うための値やメソッドを取得する際に利用

- Link href
hrefに遷移先のURLを設定する

## 参考

【2023年最新】React(v18)完全入門ガイド｜Hooks、Next.js、Redux、TypeScript(CodeMafia)

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