NEXTJS13の基本機能まとめ（Routing, Data Fetching, Server components）

## はじめに
本記事は、以下のNEXTJSのチュートリアルの内容をまとめたものである。

<iframe width="560" height="315" src="https://www.youtube.com/embed/8pzIuLFuv6U?controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## 目次
- 事前準備
- ルーティングについて
- 全ページ表示させるコンポーネントの配置先
- API呼び出しの方法について
- Server Side Renderingの特徴
- どちらのコンポーネントで実装するか　 

## 事前準備
プロジェクトの作成
```
$ npm create next-app
```
以下で実行できることを確認
```
$ npm run dev
```

## ルーティングについて
### 基本形

src/appフォルダ配下に**任意の名称のフォルダ**を作成し、そのファイル名をpage.tsxとする

これだけで、***/フォルダ名***　のページにルーティングできるようになる。
***/about*** というリソースを作成する場合は以下のようにする。

``` tsx:src/app/a¬bout/page.tsx
 export default function About(){
    return (
        <div><h1> This is about page.</h1></div>
    );
```

### ネストしたroute 

``` tsx:src/app/about/us/page.tsx
 export default function AboutUs(){
    return (
        <div><h1> This is about us page.</h1></div>
    );
```

### dynamic route
``` tsx:src/app/about/[id]/page.tsx
export default function AboutWithId({ params }: any) {
  return (
    <div>
      <h1> This is the about page with ID: {params.id} </h1>
    </div>
  );
}
```

## 全ページ共通で表示させるコンポーネントの配置
- Navbar
- Footer
など

Navbarをつけたい場合

``` tsx:src/app/components/navbar.tsx
import Link from "next/link";

export default function Navbar() {
  return (
    <div>
      <Link href="/about"> About </Link>
      <Link href="/about/us"> About Us</Link>
    </div>
  );
}

``` 

デフォルトで作成されている以下でrouteが管理されている。
{children}が、それぞれのpageの中身なので、その上にNavbarを入れればいい。

``` diff_tsx:src/app/layout.tsx

import "./globals.css";
import { Inter } from "next/font/google";
+ import Navbar from "./components/navbar";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
+       <Navbar />
        {children}
      </body>
    </html>
  );
}



``` 
## API呼び出しの方法について

### api呼び出し(x1)
```tsx:src/app/listofposts/pages.tsx

const getPostsData = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  return res.json();
};

export default async function ListOfPosts() {
  const posts = await getPostsData();
  return (
    <div>
      {posts.map((post: any) => {
        return <p> {post.title} </p>;
      })}
    </div>
  );
}
```

### api呼び出し(x2)
``` diff_tsx:src/app/listofposts/pages.tsx

const getPostsData = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  return res.json();
};

+ const getUsersData = async () => {
+  const res = await fetch("https://jsonplaceholder.typicode.com/users");
+  return res.json();
+ };

export default async function ListOfPosts() {
- const posts = await getPostsData();
+  const [posts, usres] = await Promise.all([getPostsData(), getUsersData()]);
  return (
    <div>
      {posts.map((post: any) => {
        return <p> {post.title} </p>;
      })}

+      {usres.map((user: any) => {
+        return <p> {user.name}</p>;
+      })}
    </div>
  );
}

```

## Server Side Renderingはスタティックである

サーバーコンポーネントは、ブラウザのイベントやuseState hooksなどを使用できない。
また、基本的に最初のロード時にキャッシュされるため、中の要素はスタティックになる。

### キャッシュの設定(時間設定なし)
リロード時に毎回APIを呼び出しフェッチしたい場合
``` diff_tsx:src/app/listofposts/pages.tsx
import Image from "next/image";

const getPostsData = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  return res.json();
};

const getUsersData = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  return res.json();
};

const getDogData = async () => {
-  const res = await fetch("https://dog.ceo/api/breeds/image/random");
+  const res = await fetch("https://dog.ceo/api/breeds/image/random", {
+    cache: "no-store",
+  });
  return res.json();
};

export default async function ListOfPosts() {

  const [posts, usres, dog] = await Promise.all([
    getPostsData(),
    getUsersData(),
    getDogData(),
  ]);
  return (
    <div>
      <Image src={dog.message} alt="dog" width={300} height={300} />
      {posts.map((post: any) => {
        return <p> {post.title} </p>;
      })}

      {usres.map((user: any) => {
        return <p> {user.name}</p>;
      })}
    </div>
  );
}

```

### キャッシュの設定(時間設定あり) 
指定秒数以上開けてリフレッシュされた場合に再取得したい場合
※以下例だと３秒以上開けた後ににリフレッシュした場合再フェッチされる
```tsx:
const getDogData = async () => {
  const res = await fetch("https://dog.ceo/api/breeds/image/random", {
    next: {
      revalidate: 3,
    },
  });
  return res.json();
};
```
::: note warn
他のウェブサイトやapiから画像を取得して表示させるためには、以下に取得を許可するドメインを設定する必要がある。
:::
```diff_jsx:next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
+  images: {
+    domains: ["images.dog.ceo"],
+  },
};

module.exports = nextConfig;
```

## page以外の要素
### error
サーバエラー発生時にクライアントにエラーを表示させたい場合に使用
※クライアント側のコンポーネントである必要があるため、"use client"が必要。

``` diff_tsx:src/listofposts/error.tsx
+ "use client";

export default function Error() {
  return <div> There was an error loading the data.</div>;
}
```

### loading 
ページ読み込みの際に、「Loading...」のような表示をさせたい場合に使用

```tsx:src/listofposts/loading.tsx

export default function Loading(){
  return <div> Loading...</div>;
}
```

## 目的の違い
<dl>
    <dt>クライアントコンポーネント</dt>
    <dd>
        ユーザと実際のクライアント（ブラウザ）の間を取り持つ。そのため、ブラウザイベントや、React Hooks（ブラウザのステートなど）を管理する。
    </dd>
    <dt>サーバーコンポーネント</dt>
    <dd>
        コンポーネントをサーバにレンダリングすることで、クライアントに送信しロードするjavascriptの量を減らすことができる。
    </dd>
<dl>

## 守るべきルール

:::note alert
サーバーコンポーネントをクライアントコンポーネントにインポートできない
:::

:::note info
サーバーコンポーネントをクライアントコンポーネントのpropsまたはchildrenとして渡せる
:::

## どちらのコンポーネントで実装するか

<dl>
  <dt>Client</dt>
  <dd>React hooksを使う時</dd>
  <dd>onClickなどのイベントとリスナーを使う時</dd>
  <dd>stateやeffectsなどに依存するカスタムhooksを使う場合</dd>
  <dt>Server</dt>
  <dd>センシティブな情報を保持する必要がある時（tokens, API keys など）</dd>
  <dd>バックエンドのリソースに直接アクセスする必要がある時</dd>
</dl>