Next.jsのDynamic Routesを学ぶ

## Routing
Dynamic Routesの前にNext.jsのルーティングについて簡単に説明します。

Next.jsはプロジェクト直下、もしくはsrcディレクトリ配下のpagesディレクトリに格納されているファイルを自動的にルーティングしてくれます。

例えば、下記のディレクトリ構成があったとします。

```
src/
|- pages/
    |- articles/
    |  |- index.tsx     ①
    |- admin/
       |- articles/
           |- index.tsx ②
```

本ディレクトリ構成のプロジェクトをデプロイしたウェブサイトでは、`https://example.com/articles`にアクセスすれば、①のページがレスポンスされます。その他、`https://example.com/admin/articles`にアクセスすれば、③のページがレスポンスされます。

このようにNext.jsではpages配下にディレクトリ構成に基づいて、自動的にルーティングを実現してくれます。

## Dynamic Routes
事前に用意できるファイルについては自動的にNext.js側でルーティングしてくれることがわかりました。
それでは事前に用意できないような投稿した記事の詳細画面の場合はどのようにしたら良いでしょうか。

その課題を解決する手段としてNext.jsではDynamic Routesが用意されています。

```
src/
|- pages/
    |- articles/
    |  |- index.tsx
    |  |- [id].tsx ☆
    |- admin/
       |- articles/
           |- index.tsx
```

上記ディレクトリ構成の[id].tsxのようにNext.jsでは角括弧付きでファイルを格納することで動的なルーティングを実現します。

上記がデプロイされたウェブサイトの場合、`https://example.com/articles/1`や`https://example.com/articles/aiueo`が__[id].tsx__をページとしてレスポンスします。

ちなみに[id].tsxでは指定されたパス(=id)の記事の詳細をレスポンスしたいので、下記のコードのようにrouter.queryでパスを取得して利用します。

```react
import { useRouter } from 'next/router';
import { ArticleDetail } from '~/components/atoms/articleDetail';

const ArticleDetails: NextPage = async () => {
  const router = useRouter();
  const { id } = router.query;

  const article = await fetch(`/article/${id}`).then((res) => res.json());

  return <ArticleDetail article={article} />;
};

export default ArticleDetails;
```

## getStaticPaths及びgetStaticPropsを利用する方法
Next.jsではgetStaticPropsを利用することでSSGを実現できます。

単純にパスを指定可能なファイルについてはgetStaticPropsを用いて下記のように記述することでSSGを実現します。

src/pages/article/index.tsx

```react
import { NextPage } from 'next';
import { ArticleCard } from '~/components/atoms/articleCard';
import { getAllArticle } from '~/lib/article';

interface Article {
  // オブジェクトの型指定
}

type Props = {
  articles: Article[];
};

const ArticleList: NextPage<Props> = ({ articles }) => {
  return (
      <div>
          {articles.map((article) => (
            <ArticleCard key={article.id} article={article} />
          ))}
      </div>
  );
};

export const getStaticProps = async() => {
  const articles: Articles[] = await getAllArticle();

  return {
    props: { articles },
    revalidate: 3,
  };
}

export default ArticleList;
```

ビルド時にgetStaticPropsは取得したデータをpropsとして返却します。返却されたpropsはページが受け取って、そのデータを元に静的サイトを生成します。

また、propsと同時に返却している`revalidate`については[こちらの記事](https://qiita.com/buttakyou/items/a98244cdb88135271bed)を参考にしてください。

次に、事前にパスを指定できない記事詳細ページについては`getStaticPaths`を用いて下記のように記述することでSSGを実現します。

src/pages/article/[id].tsx

```react
import { useRouter } from 'next/router';
import { NextPage } from 'next';
import { ArticleDetail } from '~/components/atoms/articleDetail';
import { ErrorPage } from '~/components/general'
import { getAllArticleId, getArticleById } from '~/lib/article';

interface Article {
  // オブジェクトの型指定
}

type Props = {
  article: Article;
};

const ArticleDetail: NextPage<Props> = ({ article }) => {
  const router = useRouter();

  // getStaticPropsでまだデータ取得できていない場合にrouter.isFallbackがtrue
  if (router.isFallback) return <div>Loading...</div>;

  // データが存在しない場合
  if(!article) return <ErrorPage code={404}/>;

  return (
    <div>
      <ArticleDetail article={article}/>
    </div>
  );
}

export const getStaticPaths = () => {
  const paths = await getAllArticleId();

  return {
    paths,
    fallback: true,
  };
}

export const getStaticProps = (paths: { params: { id: number } }) => {
  const article = await getArticleById(paths.params.id);

  return {
    props: {
      article,
    },
    revalidate: 3,
  };
}

export default ArticleDetail;
```

このようにgetStaticPropsとは別にgetStaticPathsを定義します。

ビルド時にgetStaticPathsはパスとなるidを取得してpathsとして返却します。

返却されたpathsに1から10までのidが含まれていた場合、`1.tsx`や`2.tsx`のように__[id]__に1から10が割り当てられたファイルが生成されます。

1.tsxの場合はidとして`１`がgetStaticPropsに引数として渡されます。
その後は前項に記載したようにgetStaticPropsは取得したデータをpropsとして返却します。返却されたpropsはページが受け取って、そのデータを元に静的サイトを生成します。

このようにしてSSGの場合もDynamic Routesを実現します。

ちなみに何もしなければ、ビルド後に追加されたidの詳細ページを追加できません。
getStaticPathsの返却する値に`fallback`を追加して、trueもしくは'blocking'を指定することでビルドされていないパスに指定された場合に再ビルドさせることができます。

fallbackの詳細については[公式サイト](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required)をご参照ください。

## 最後に
記事の内容がわかりづらければ、ご指摘いただけますと幸いです。