Next.js × microCMSでブログサイト作成した際に学んだことを書き留める

# はじめに
ブログサイトを作る過程で学んだことを、備忘録目的で投稿しています。
自身は駆け出しエンジニアであり、React自体がほぼ初学者のため、誤った認識・理解をしている可能性があります。
万が一参考にする場合は、上記の点を考慮した上でご一読ください。

また、スタイリングについては割愛しています。

# 目的
Next.jsを利用し、SSG（Static Site Generation）を実現したブログサイトを作成する。
記事投稿にはmicroCMSを採用する。
ホスティングサービスはVercelを利用し、記事投稿に合わせて自動でデプロイし、サイト表示が更新されるようにする。

# 作業環境
Windows10 Pro x64
Node.js: 20.14.0
npm: 10.3.0
Next.js: 14.2.3（App Router使用）

# 手順
## 1. プロジェクトの準備
### Next.jsのインストール
下記コマンドを実行して自動セットアップを行う。
```console
npx create-next-app@latest
```
```console
√ What is your project named? ... project_name
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like to use `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to customize the default import alias (@/*)? ... No / Yes
```
今回は上から順に下記のように設定しました。
1. プロジェクト名　→　任意の名前を入力
2.  TypeScriptの使用　→　No
3.  ESLintの使用　→　Yes
4.  Tailwind CSSの使用　→　No
5.  `src` ディレクトの使用　→　Yes
6.  の使用　→　No

インストール完了後は `npm run dev` コマンドで http://localhost:3000 の開発サーバーを立ち上げて確認します。

### Sassのインストール、JSXに変更
`npm i -D sass` を実行して`Sass`をインストールし、その後`css`ファイルを`scss`ファイルに変更します。
また、`layout.js`、`page.js`をそれぞれ`jsx`ファイルに変更します。
CSS modulesを採用した構成にしています。

## 2. ページの作成
まずは基本となる骨格を作成しました。
各JSXファイルは下記のようにしました。
Next.jsでは画像に`next/image`、リンクには`next/link`を使用することでパフォーマンスが向上します。

```src/app/layout.jsx
import "./globals.scss";

export const metadata = {
  title: "",
  description: "",
};

export default function RootLayout({ children }) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  );
}
```

```src/app/page.jsx
import Image from "next/image";
import Link from "next/link";
import styles from "./page.module.scss";

export default function Home() {
  return (
    <main className={styles.main}>
      <h1>記事一覧</h1>
      <ul className={styles.cards}>
        <li className={styles.card}>
          <Link href="" className={styles.link}>
            <p className={styles.thumbnail}>
              <Image src="" alt="" height="" width="" />
            </p>
            <div>
              <h2 className={styles.title}>記事タイトル01</h2>
              <p>カテゴリ名</p>
            </p>
          </Link>
        </li>
      </ul>
    </main>
  );
}
```

importには`@`から始まるエイリアスを使用し、importをわかりやすくしています。
エイリアス設定は`jsconfig.json`で変更できます、今回は初期のままにしています。
```jsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```

次にこれらをコンポーネント化しました。
ディレクトリ構成を自分好みに変更、JSXファイルの記述も修正し、下記のようにしました。
下記のディレクトリ構成は変更を加えたもの、これからの説明に必要な箇所のみになります。

```
root/
　├ src/
　│　├ app/
　│　│  ├ globals.scss
　│　│  ├ layout.jsx
　│　│  ├ page.jsx
　│　│  └ page.scss
　│　│
　│　├ components/
　│　│  ├ components/
　│　│  │  ├ index.jsx
　│　│  │  └ index.module.scss
　│　│  │
　│　│  └ components/
　│　│     ├ index.jsx
　│　│     └ index.module.scss
　│　│
　│　└ public/
　└ jsconfig.json/
```

```src/app/page.jsx
import styles from "./page.module.scss";
import { Cards } from "@/components/Cards";

export default function Home() {
  return (
    <main className={styles.main}>
      <h1>記事一覧</h1>
      <ul className={styles.cards}>
        <Cards />
      </ul>
    </main>
  );
}
```

```src/components/Cards.jsx
import styles from "./index.module.scss";
import { Card } from "@/components/Card";

export const Cards = () => {
  return (
    <ul className={styles.cards}>
      <Card />
    </ul>
  );
};
```


```src/components/Card.jsx
import Image from "next/image";
import Link from "next/link";
import styles from "./index.module.scss";

export const Card = () => {
  return (
    <li className={styles.card}>
      <Link href="" className={styles.link}>
        <p className={styles.thumbnail}>
          <Image src="" alt="" height="" width="" />
        </p>
        <div>
          <h2 className={styles.title}>記事タイトル01</h2>
          <p>カテゴリ名</p>
        </p>
      </Link>
    </li>
  );
};
```

## 3. microCMSの準備
microCMSにアカウント登録します。
「一から作成する」を選択し、サービス情報を入力したらサービスを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/fa94c6cc-7fdf-d2c2-b9bc-7730e8ddc083.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/db25d42e-edcd-6a4c-862b-41b181777e1c.png)

早速APIを作成します。
今回はテンプレートの「ブログ」を使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/caf31377-ee6d-5507-48a6-f1150ee8e131.png)

APIが作成できたので、自分好みに変更していきます。
右上の「API設定」をクリックし、下記のように変更しました。
▼基本情報
API名：ブログ → 記事
エンドポイント：blogs → articles

▼APIスキーマ
カテゴリ：category
タイトル：title
内容 → 本文：content → body
アイキャッチ → サムネイル画像：eyecatch → thumbnail

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/75dc8e80-4a85-1255-c26a-a3df838218ac.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/24822ec5-d578-b2b0-0d28-91e6233c1a1f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/a46c3184-b38b-3802-6979-d81f7ffd4b85.png)



カテゴリは登録情報を下記画像のように変更しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/a9af6e12-4956-1643-246e-a9307998aa15.png)



## 4. microCMSのAPIキーとサービスドメインを準備
APIへの接続を簡単に行えるようmicrocms-js-sdkをインストールします。
```
npm install microcms-js-sdk
```

microCMSのAPIキーを確認します、右上のAPIプレビューから確認できます。
![microcms_apikey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/a7ec33ba-583b-1144-6ae3-79dc0f21a1c2.png)

`.env.local`ファイルを作成し、microCMSのサービスドメイン名はとAPIキーを記載します。
サービスドメイン名はmicroCMSのURLでも確認でき、下記の箇所になります。
`https://サービスドメイン名.microcms.io/`
```
MICROCMS_SERVICE_DOMAIN=microCMSの登録したサービスドメイン名を入力
MICROCMS_API_KEY=microCMSのAPIキーを入力
```

## 5. コンテンツの取得
### API取得用のファイルを準備
microCMSからコンテンツを取得するファイルを作成します。
`libs`フォルダを作成し、その中に`microcms.js`ファイルを作成して記述していきます。
下記のようにしました。

```/libs/microcms.js
import { createClient } from "microcms-js-sdk";
import { notFound } from "next/navigation";

if (!process.env.MICROCMS_SERVICE_DOMAIN) {
  throw new Error("MICROCMS_SERVICE_DOMAIN が設定されていません。");
}
if (!process.env.MICROCMS_API_KEY) {
  throw new Error("MICROCMS_API_KEY が設定されていません。");
}

// API取得用のクライアントを作成
export const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: process.env.MICROCMS_API_KEY,
});

// 記事一覧を取得
export const getArticlesList = async (queries) => {
  try {
    const response = await client.getList({
      endpoint: "articles",
      queries,
    });
    return response;
  } catch (error) {
    console.error("getArticlesListでエラーが発生しました => ", error);
    notFound();
  }
};

// 記事詳細を取得
export const getArticlesDetail = async (contentId, queries) => {
  try {
    const response = await client.getListDetail({
      endpoint: "articles",
      contentId,
      queries,
    });
    return response;
  } catch (error) {
    console.error("getArticlesDetailでエラーが発生しました => ", error);
    notFound();
  }
};

```

#### 解説
最初に`microcms-js-sdk`の読み込み、そしてサービスドメイン、APIキーの設定を行います。
`.env.local`にサービスドメイン、APIキーが設定されていない場合はエラーを返します。
`notFound`はAPI取得エラーが発生した場合に、404エラーページを表示するために使用します。
```/libs/microcms.js
import { createClient } from "microcms-js-sdk";
import { notFound } from "next/navigation";

if (!process.env.MICROCMS_SERVICE_DOMAIN) {
  throw new Error("MICROCMS_SERVICE_DOMAIN が設定されていません。");
}
if (!process.env.MICROCMS_API_KEY) {
  throw new Error("MICROCMS_API_KEY が設定されていません。");
}

// API取得用のクライアントを作成
export const client = createClient({
  serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: process.env.MICROCMS_API_KEY,
});
```

`endpoint`にはmicroCMSに設定したエンドポイントを入力します、microCMSのAPI設定から確認できます。
`queries`は、コンテンツを取得する際に絞り込みなどの機能を活用するために使用します。
コンテンツ取得エラーが発生した場合には、`notFound`で404エラーページを表示するようにしています。

記事詳細のデータ取得には、`contentId`を送信することで、必要な記事のみのデータを取得することができます。
```/libs/microcms.js
// 記事一覧を取得
export const getArticlesList = async (queries) => {
  try {
    const response = await client.getList({
      endpoint: "articles",
      queries,
    });
    return response;
  } catch (error) {
    console.error("getArticlesListでエラーが発生しました => ", error);
    notFound();
  }
};

// 記事詳細を取得
export const getArticlesDetail = async (contentId, queries) => {
  try {
    const response = await client.getListDetail({
      endpoint: "articles",
      contentId,
      queries,
    });
    return response;
  } catch (error) {
    console.error("getArticlesDetailでエラーが発生しました => ", error);
    notFound();
  }
};
```

### コンテンツを取得
作成した`getArticlesList`関数を呼び出して、ブログ記事の一覧データを取得します。
レスポンスオブジェクトには`contents`プロパティがあり、記事データが格納されています。
わかりやすいように、`contents`を`articles`という名前に変更して使用します。
取得したデータをpropsで渡してあげます。

```/src/app/page.jsx
import { notFound } from "next/navigation";
import styles from "./page.module.scss";
import { Cards } from "@/components/Cards";
import { getArticlesList } from "@/libs/microcms";

export default async function Home() {
  // ブログ一覧を取得
  const articlesListResponse = await getArticlesList().catch(() => notFound());
  const { contents: articles } = articlesListResponse;
  return (
    <main className={styles.main}>
      <h1>記事一覧</h1>
      <ul className={styles.cards}>
        <Cards articles={articles} />
      </ul>
    </main>
  );
}
```

```/src/components/Cards/index.jsx
import styles from "./index.module.scss";
import { Card } from "@/components/Card";

export const Cards = async ({ articles }) => {
  return (
    <ul className={styles.cards}>
      {articles.map((article) => (
        <Card article={article} key={article.id} />
      ))}
    </ul>
  );
};
```

```/src/components/Card/index.jsx
import Image from "next/image";
import Link from "next/link";
import styles from "./index.module.scss";

export const Card = async ({ article }) => {
  return (
    <li className={styles.card}>
      <Link href={`/articles/${article.id}`} className={styles.link}>
        <p className={styles.thumbnail}>
          <Image
            src={article.thumbnail.url}
            alt={article.title}
            height={article.thumbnail.height}
            width={article.thumbnail.width}
            priority
          />
        </p>
        <div>
          <h2 className={styles.title}>{article.title}</h2>
          <p className={styles.category}>{article.category.name}</p>
        </div>
      </Link>
    </li>
  );
};
```
この状態で、`npm run dev`を実行し、ページを表示してみると下記エラーが発生しました。
> Error: Invalid src prop (https://images.microcms-assets.io/assets/a6a4f0e5be594b3c849be21f97efbe92/2cfd5d0adda9492190fc04404ac789fb/blog-template.png) on `next/image`, hostname "images.microcms-assets.io" is not configured under images in your `next.config.js`
See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host

ChatGPTに対処法を教えてもらいました。
> このエラーは、Next.jsの**`next/image`**コンポーネントを使用しているときに発生します。**`next/image`**は画像の最適化と提供を行うためのものですが、セキュリティとパフォーマンスの理由から、外部の画像ソースを使用する前に設定ファイル（**`next.config.js`**）でそのホスト名を明示的に許可する必要があります。
>
> エラーメッセージにある通り、**`https://images.microcms-assets.io`**から画像を読み込もうとしていますが、このホスト名が**`next.config.js`**に設定されていないため、エラーが発生しています。
>
> この問題を解決するには、**`next.config.js`**ファイルを編集して、**`images`**セクションに**`domains`**配列を追加し、その中に**`"images.microcms-assets.io"`**を含める必要があります。

`next.config.js`を`next.config.mjs`に拡張子を変更し、内容を下記のように変更しました。
```javascript:next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "images.microcms-assets.io",
      },
    ],
  },
};

export default nextConfig;
```

再度、ページを確認すると無事データを取得できるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/60963d95-8110-c85e-8cd8-905869a3066a.png)

詳細ページも作成します。
ディレクトリ構成を下記のようにし、詳細ページ用の`page.jsx`を作成します。

```
root/
　├ src/
　│　├ app/
　│　│  ├ articles/
　│　│  │  └ [slug]/
　│　│  │     ├ page.jsx
　│　│  │     └ page.scss
　│　│  │
　│　│  ├ globals.scss
　│　│  ├ layout.jsx
　│　│  ├ page.jsx
　│　│  └ page.scss
　│　│
　│　├ components/
　│　│  ├ components/
　│　│  │  ├ index.jsx
　│　│  │  └ index.module.scss
　│　│  │
　│　│  └ components/
　│　│     ├ index.jsx
　│　│     └ index.module.scss
　│　│
　│　├ libs/
　│　│  └ microcms.js
　│　│
　│　└ public/
　│ 
　├ env.local
　├ jsconfig.json
　└ next.config.mjs
```

```/src/app/articles/[slug]/page.jsx
import Image from "next/image";
import styles from "./page.module.scss";
import { getArticlesDetail } from "@/libs/microcms";

export default async function Page({ params }) {
  // URLパラメータのIDを参照して、ブログの詳細を取得
  const article = await getArticlesDetail(params.slug);
  return (
    <main className={styles.main}>
      <div className={styles.article}>
        <p className={styles.thumbnail}>
          <Image
            src={article.thumbnail.url}
            alt={article.title}
            height={article.thumbnail.height}
            width={article.thumbnail.width}
            priority
          />
        </p>
        <h1 className={styles.title}>{article.title}</h1>
        <p className={styles.category}>{article.category.name}</p>
        <div className={styles.body}>{article.body}</div>
      </div>
    </main>
  );
}
```
`params`はNext.jsの機能の一部であり、ルーティング時にURLパラメータを取得するために使用されます。
試しに`params`の中身がどうなっているのがコンソールに表示してみると下記の通りになっています。
```javascript
console.log("params => ", params);

// 出力結果
params =>  { slug: 'yy3_bxzq2f-0' }
```

ではこれで詳細ページを表示してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/5ea97f05-fca7-3fe3-2ae4-80ba14b3bd05.png)

データを取得できましたが、本文にHTML文字列がそのまま表示されてしまっています。
これを解決するためには、`html-react-parser`をインストールして使用します。
`html-react-parser`は、HTML文字列をReactコンポーネントに変換するためのライブラリです。
下記コマンドでインストールしたら、ファイルを修正します。
```console
npm install html-react-parser
```
```/src/app/articles/[slug]/page.jsx
import Image from "next/image";
import styles from "./page.module.scss";
import { getArticlesDetail } from "@/libs/microcms";

export default async function Page({ params }) {
  // URLパラメータのIDを参照して、ブログの詳細を取得
  const article = await getArticlesDetail(params.slug);
  return (
    <main className={styles.main}>
      <div className={styles.article}>
        <p className={styles.thumbnail}>
          <Image
            src={article.thumbnail.url}
            alt={article.title}
            height={article.thumbnail.height}
            width={article.thumbnail.width}
            priority
          />
        </p>
        <h1 className={styles.title}>{article.title}</h1>
        <p className={styles.category}>{article.category.name}</p>
        <div className={styles.body}>{article.body}</div>
      </div>
    </main>
  );
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/b8ebf33d-fd9a-4e39-d572-9b46affc005e.png)

### 取得件数を設定する
microCMSでは、`limit`パラメータを使用することで取得件数を指定することができます。
実はデフォルト値では`limit`の値が`10`となっており、10件までしか取得できないので11件以上取得する場合は変更が必要です。
また、上限値は`100`となっており、101件以上取得するには[microCMS公式のヘルプ（101件以上のコンテンツを取得するにはどうしたらよいですか？）](https://help.microcms.io/ja/knowledge/fetch-big-data)で紹介されていますのでそちらを参照ください。

`limit`パラメータはクエリパラメータとして指定できます。
試しに1件のみ取得するように設定します。
```/src/app/page.jsx
import { notFound } from "next/navigation";
import styles from "./page.module.scss";
import { Cards } from "@/components/Cards";
import { getArticlesList } from "@/libs/microcms";

export default async function Home() {
  // ブログ一覧を取得
  const queries = { limit: 1 };
  const articlesListResponse = await getArticlesList(queries).catch(() =>
    notFound()
  );
  const { contents: articles } = articlesListResponse;
  return (
    <main className={styles.main}>
      <h1>記事一覧</h1>
      <ul className={styles.cards}>
        <Cards articles={articles} />
      </ul>
    </main>
  );
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/95eaa18c-87db-2800-c1a0-9850b03cbc28.png)

1件のみの取得に成功しました。
将来的にページネーション機能を追加し、例えば「1ページにつき12件まで表示」といった設定を行う場合、取得件数の設定を一箇所で管理できると保守性が良いので、`limit`の値を管理するためのファイルを作成し、それを読み込み使用します。
管理するファイルは`constants`フォルダを作成し、その中で管理するようにしました。

```/src/constants/index.js
// 1ページの表示件数
export const LIMIT = 12;
```
```/src/app/page.jsx
import { notFound } from "next/navigation";
import styles from "./page.module.scss";
import { LIMIT } from "@/constants";
import { Cards } from "@/components/Cards";
import { getArticlesList } from "@/libs/microcms";

export default async function Home() {
  // ブログ一覧を取得
  const queries = { limit: LIMIT };
  const articlesListResponse = await getArticlesList(queries).catch(() =>
    notFound()
  );
  const { contents: articles } = articlesListResponse;
  return (
    <main className={styles.main}>
      <h1>記事一覧</h1>
      <ul className={styles.cards}>
        <Cards articles={articles} />
      </ul>
    </main>
  );
}

```


これでmicroCMSからのデータ取得の基本は完了です。

## 6. SSGに対応する
現段階では、一覧ページはSSG（Static Site Generation）の静的なファイルとして生成してくれていますが、各記事の詳細ページはSSR（Server-Side Rendering）のリクエストごとにサーバーでページをレンダリングする手法になっています。

試しに、現在の状態で`npm run build`を実行すると、コンソールには次のように表示されます。
```console
> next build

  ▲ Next.js 14.2.3
  - Environments: .env.local

   Creating an optimized production build ...
 ✓ Compiled successfully
 ✓ Linting and checking validity of types    
 ✓ Collecting page data    
 ✓ Generating static pages (5/5)
 ✓ Collecting build traces
 ✓ Finalizing page optimization

Route (app)                              Size     First Load JS
┌ ○ /                                    7.07 kB        99.2 kB
├ ○ /_not-found                          871 B          87.8 kB
└ ƒ /articles/[slug]                     285 B          92.4 kB
+ First Load JS shared by all            87 kB
  ├ chunks/23-0627c91053ca9399.js        31.5 kB
  ├ chunks/fd9d1056-2821b0f0cabcd8bd.js  53.6 kB
  └ other shared chunks (total)          1.88 kB


○  (Static)   prerendered as static content
ƒ  (Dynamic)  server-rendered on demand
```
`ƒ /articles/[slug]`となっており、`ƒ`マークはSSRであることを表しています。
ここからは詳細ページもSSGで事前にHTMLの静的ファイルが生成されるようにファイルを編集します。
SSGにするためには、`generateStaticParams`を使用します。
`generateStaticParams`はNexet.jsのApp Routerで導入された機能で、動的ルートのために静的パスを生成するために使用されます。
この機能は、以前のバージョンのNext.jsで使用されていた`getStaticPaths`メソッドの代わりに利用されます。

```/src/app/articles/[slug]/page.jsx
import Image from "next/image";
import parse from "html-react-parser";
import styles from "./page.module.scss";
import { getArticlesList, getArticlesDetail } from "@/libs/microcms";

export async function generateStaticParams() {
  // ブログ一覧をAPI経由で取得します
  // 取得件数には最大値である`100`を設定（初期値：10）
  // データ取得量の削減のため、コンテンツ内の'id'のみ取得
  const queries = { limit: 100, fields: "id" };
  const articlesListResponse = await getArticlesList(queries);

  // `articlesListResponse`の`contents`を`articles`に代入しています。
  const { contents: articles } = articlesListResponse;
  // 各ブログポストのIDを用いて、必要なパラメータオブジェクトを作成
  // 'articleId'キーに対応する値として'article.id'を設定
  // これにより、各生成されるページに対して、どのブログポストのデータを
  // 取得して表示するかを指定するための情報を提供します
  const paths = articles.map((article) => {
    return {
      slug: article.id,
    };
  });
  return [...paths];
}

export default async function Page({ params }) {
  // URLパラメータのIDを参照して、ブログの詳細を取得
  const article = await getArticlesDetail(params.slug);
  return (
    <main className={styles.main}>
      <div className={styles.article}>
        <p className={styles.thumbnail}>
          <Image
            src={article.thumbnail.url}
            alt={article.title}
            height={article.thumbnail.height}
            width={article.thumbnail.width}
            priority
          />
        </p>
        <h1 className={styles.title}>{article.title}</h1>
        <p className={styles.category}>{article.category.name}</p>
        <div className={styles.body}>{parse(article.body)}</div>
      </div>
    </main>
  );
}

```
さきほどの取得件数（`limit`）でも説明しましたが、取得件数のデフォルト値は`10`件となっているので、最大値である`100`を指定しています。
取得する情報はコンテンツID以外不要なので、`fields`で`id`のみを取得し、データ量の削減をしています。

この状態で、試しに`paths`の中身がどうなっているのかコンソールに表示してみます。
```console
console.log("paths => ", paths);
// 出力結果
paths =>  [ { slug: '2la1jxzdhmy' }, { slug: 'yy3_bxzq2f-0' } ]
```
この状態で`num run build`を実行します。
```console
> next build

  ▲ Next.js 14.2.3
  - Environments: .env.local

   Creating an optimized production build ...
 ✓ Compiled successfully
 ✓ Linting and checking validity of types    
 ✓ Collecting page data    
 ✓ Generating static pages (7/7)
 ✓ Collecting build traces
 ✓ Finalizing page optimization

Route (app)                              Size     First Load JS
┌ ○ /                                    7.07 kB        99.2 kB
├ ○ /_not-found                          871 B          87.8 kB
└ ● /articles/[slug]                     285 B          92.4 kB
    ├ /articles/2la1jxzdhmy
    └ /articles/yy3_bxzq2f-0
+ First Load JS shared by all            87 kB
  ├ chunks/23-0627c91053ca9399.js        31.5 kB
  ├ chunks/fd9d1056-2821b0f0cabcd8bd.js  53.6 kB
  └ other shared chunks (total)          1.88 kB


○  (Static)  prerendered as static content
●  (SSG)     prerendered as static HTML (uses getStaticProps)
```
先ほどまで`ƒ /articles/[slug] `であったのが、` ● /articles/[slug] `に変わっており、SSGになっていることが確認できます。


## 7. Vercelでホスティング
前提条件
- 事前にデプロイを行うGitHubのリポジトリを用意していること
- Vercelにサインアップしていること

### Vercelのリポジトリ一覧にプロジェクトを追加
ダッシュボード内の「Add New…」ボタンをクリックし、プルダウンの中から「ProJect」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/f2e60daa-bd34-6351-40b3-11d59b373179.png)




「Continue With GitHub」をクリックしてVercel1とGit Hubを連携する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/680d4c60-cbdb-6e45-4e62-e65f93db6493.png)


「Install」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/052e0c85-decb-431a-579a-45e6bd60fd92.png)


GitHub内の全てのリポジトリをVercelでインポート可能にする場合は「All repositories」、特定のリポジトリのみインポート可能にする場合は「Only select repositories」を選択。

今後Vercelで他にもホスティングしていきたいので「All repositories」を選択しました。
選択したら「Install」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/3dbcd6a6-1891-ba2e-f56f-731fdfbbd671.png)



### Vercelでデプロイする
「Import」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/d3d9e58e-a193-919c-7120-dc55fb32b3c9.png)




プロジェクト名を変更する場合は「Project Name」を変更

microCMSと連携するために環境変数の設定を行う。
ここでは、`.env.local`ファイルで入力した環境変数と同じ内容を入力する。
「Environment Variables」を開く。
「Key」と「Value (Will Be Encrypted)」項目を入力。
`MICROCMS_SERVICE_DOMAIN`と`MICROCMS_API_KEY`の2つを追加する。
入力したら「Deploy」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/58c7079d-2992-3eda-d823-ff40b049b28b.png)



デブロイが始まる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/7df15c0c-ccc7-2142-b595-80c728bf8588.png)


デブロイが成功すると「Congratulations!」というテキストとともに紙吹雪が舞う。
デプロイしたプロジェクトがホスティングできているかを確認する。
表示されているページ画像をクリックするとホスティングされたページへ飛ぶ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/40dcb746-76be-f421-3ce1-550f93ed2c67.png)



#### API_KEYを後から追加する場合
プロジェクトのダッシュボードから上部メニューの「Settings」を開く
左メニューから「Environment Variables」を開く
「Key」と「Value」を入力して「Save」で完了


## 8. 記事投稿で自動デプロイ
記事更新時に自動デプロイが行われ、自動でサイト更新がされるように設定します。

### VercelでWeb HookのURを発行
Vercelのダッシュボートから該当プロジェクトを開き、上部メニューから「Settings」を開く
左メニューから「Git」を開き、その中の「Deploy Hooks」項目にある「Create Hook」を入力する
左入力エリアの「My Example Hook」には好きな名前を入力。（今回は「main」としました。）
右入力エリアにはブランチ名を入力。（例：main [または master]）
入力したら「Create Hook」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/c9efd091-98e6-c389-da88-799513dff129.png)

そうするとWeb HookのURLが発行されるのでコピーする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/cb3ac260-dcac-e759-86d2-ba11761d1533.png)

### microCMSでWebhook登録
microCMSの管理画面を開き、左の「コンテンツ（API）」から連携したいAPIを開く
右上の「API設定」を開き、左メニューから「Webhook」を開き、「追加」ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/22552306-542e-e5c1-31f0-82ff9b5bca42.png)

Webhook通知サービスの選択が表示される、ここで一番下の「カスタム通知」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/a37985d5-a72a-d158-f782-33fbcec76aaf.png)


下記の通り設定しました。
Webhookの名前：好きな名前を入力（例：blog）
URL：Vercelで発行したWeb Hook URL
「通知タイミングの設定」はどのタイミングでデプロイするかの設定になるはずです。
今回は初期設定のままにしておきます。
最後に「設定する」をクリックして完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/99b40cba-194b-4295-ab00-4f9df1ec09a7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/95569c6d-fac2-79df-4770-6f4078a93966.png)

### 確認
microCMSで記事を投稿するとVercelで自動デプロイされてサイト更新がされるか確認します。
microCMSで適当な記事を作成し公開します。


その後、Vercelで確認します。
自動的にbuildが実行され、そのまま問題なければ完了します。
サイトを確認すると、表示が更新されているのを確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/5e469994-5aa5-49cb-b9e3-beff4742433b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/7e93cafe-c91e-8146-e9e7-05eee9983f47.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/adb8ba4b-81fe-44ad-1193-46fa21ffbf7d.png)


# さいごに
Next.js × microCMS の基本をまとめました。
他にもカテゴリページの作成、メタデータ生成、サイトマップ生成などやれることはありますが、書くとキリがないので今回はここまでのご紹介とさせていただきます。

microCMS公式では、Next.jsとの連携方法についてチューリアルが用意されていますので、そちらを参考にすると体得しやすいと思います。
また、microCMSがブログテンプレートのGitHubも公開されているので、そちらもぜひ参考にしてみてください。



# 参考
[【完全版】App Routerで最初に知っておくとよさそうな基礎を全部まとめてみた](https://rakuraku-engineer.com/posts/nextjs-app/)
[【初心者OK】Next.jsで自作ブログを作ってみよう【MicroCMSを利用】](https://www.youtube.com/watch?v=dNpONz4Yi04)
[チュートリアル（App Router）｜microCMSドキュメント](https://document.microcms.io/tutorial/next/next-app-router-tutorial)
[microCMS + Next.jsでJamstackブログを作ってみよう](https://blog.microcms.io/microcms-next-jamstack-blog/#hb67e9ac3f6)
[Vercelの新規登録からNext.jsデプロイまで](https://zenn.dev/icck/articles/f7aa3f5304cd1a)
[Vercelで環境変数を設定する](https://www.snorerelax.com/posts/tech-vercel-environment/)