【Next.js】動的ルーティングと個別ページのSSG

## はじめに
本記事は、プログラミング初学者が学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。  
そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。  
間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。  

## 動的ルーティングと個別ページのSSG


```post.js
// ディレクトリを取得するためにインポート
import path from "path";
import fs from "fs";
// メタデータを分析をするためにインポート
import matter from "gray-matter";
import { remark } from "remark";
import html from "remark-html";

// カレントディレクトリとpostsのpathを結合→cwd/posts
const postsDirectory = path.join(process.cwd(), "posts");

// mdファイルのデータを取り出すための関数
export function getPostsData() {
  // postDirectory(= cwd/postsディレクトリ内のファイル名が配列で入っている)をfs.readdirSyncで読み出してfileNamesに格納
  const fileNames = fs.readdirSync(postsDirectory);
  // fileNamesからファイル名を一つずつ取り出す
  const allPostData = fileNames.map((fileName) => {
    // 取り出す際にreplace関数で拡張子を取り除く
    // 第一引数に/\.md$/を指定し、第二引数に空文字を指定することで除外することができる
    const id = fileName.replace(/\.md$/, "");  // ファイル名＝id

    // マークダウンファイルを文字列として読み取る
    // postDirectory(= cwd/posts)とfileName(= ssg-ssr等)を結合してfullPathに格納
    const fullPath = path.join(postsDirectory, fileName);
    // それぞれの.mdファイルの内容を読み出し。文字列で読み出したいのでutf8を指定
    const fileContents= fs.readFileSync(fullPath, "utf8");
    // マークダウンファイルのメタデータの分析
    // matterResultには.mdのtitle等がオブシェトの配列として格納されている
    const matterResult = matter(fileContents);
    // idおよびデータを返す(allPostData)
    // matterResult.dataはtitle,date,thumbnailが入っている
    return {
      id,
      ...matterResult.data,
    }
  });
  // getPostsDataに関する返り値
  return allPostData;
}

// 動的ルーティングを実現するためにgetStaticPathのreturnで使用するpathを取得するための関数
export function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory);
  return fileNames.map((fileName) => {
    return {
      params: {
        // 動的ルーティングにidを指定しているため、idをreturnする
        id: fileName.replace(/\.md$/, ""),
      },
    };
  });

// idに基づいて記事のデータを取得するための関数
export async function getPostData(id) {
  // pwd/posts/ファイル名.mdをfullPathに格納
  const fullPath = path.join(postsDirectory, `${id}.md`);
  // fullPathを指定して.mdファイルの中身を読み込む
  const fileContent = fs.readFileSync(fullPath, "utf8");

  const matterResult = matter(fileContent)
  // matterResult.contentは記事の中身
  const blogContent = await remark().use(html).process(matterResult.content);

  // blogContentをstringにする
  const blogContentHTML = blogContent.toString();

  return {
    id,
    blogContentHTML,
    ...matterResult.data,
  };
}

```

```index.js
import Head from 'next/head'
import Link from 'next/link'
import Layout, { siteTitle } from '../components/Layout'
import { getPostsData } from '../lib/post'
import styles from '../styles/Home.module.css'
import utilStyles from '../styles/utils.module.css'

// SSGのための記述
export async function getStaticProps() {
  // データの取得先を指定
  const allPostsData = getPostsData();  // getPostsDataはid,title,date,thumbnail
  console.log(allPostsData);
  // Homeコンポーネントに渡すための記述
  return {
    props: {
      allPostsData,
    }
  }
}


export default function Home({ allPostsData }) {
  return (
    <Layout>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <p>サンプルの文章</p>
      </section>
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2>記事サンプル</h2>
        <div className={styles.grid}>
         {allPostsData.map(({ id, title, date, thumbnail }) => (
            <article key={id}>
              <Link href={`/posts/${id}`}>
                <img
                  src={`${thumbnail}`}
                  className={styles.thumbnailImage}
                />
              </Link>
              <Link href={`/posts/${id}`}>
                <a className={utilStyles.boldText}>
                  {title}
                </a>
              </Link>
              <br />
              <small className={utilStyles.lightText}>
                {date}
              </small>
            </article>
         ))}
        </div>
      </section>
    </Layout>
  )
}
```

```[id].js
import Head from "next/head";
import Layout from "../../components/Layout";
import { getAllPostIds, getPostData } from "../../lib/post";
import utilStyles from '../../styles/utils.module.css';

// getStaticPathsを使用する
// getStaticPathsにはデータのPathが必要になる→Post.jsでPathを返す関数を準備する
export async function getStaticPaths() {
  // pathを指定する。ここではpost.jsで設定したgetAllPostIdsを指定。
  const paths = getAllPostIds();
  return {
    paths,
    fallback: false,
  };
}

// getStaticPathsはgetStaticPropsと同時に使用する必要がある
export async function getStaticProps({ params }) {
  // getPostDataにはidを渡す必要があるため(params.id)と記述
  const postData = await getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}

export default function Post({ postData }) {
  return (
    <Layout>
      <Head>
        <title>{postData.title}</title>
      </Head>
      <article>

      </article>
        <h1 className={utilStyles.headingX1}>{postData.title}</h1>
        <div className={utilStyles.lightText}>{postData.date}</div>
        {/* HTMLを表示させるための記述。サンプルのため以下のみ */}
        <div dangerouslySetInnerHTML={{__html: postData.blogContentHTML}} />
    </Layout>
  );
}
```