Next.js + microCMS でブログをSSGする

## 事の起こり
クライアント側でAPIを叩いて記事情報を取得しブログを作成したところ、GAでタイトルが取れなかったためプリレンダリングできるようにして解決を図りました。

## microCMSのSDKを使う
https://github.com/microcmsio/microcms-js-sdk

`getStaticPaths`内などでmicroCMSのAPIを叩くときに使います。`cmsClient`と名前をつけて呼び出せるように設定用のファイルを用意します。

```lib/microcms.ts
import { createClient } from 'microcms-js-sdk'

export const cmsClient = createClient({
  serviceDomain: process.env.NEXT_PUBLIC_MICRO_CMS_SERVICE_DOMAIN || '',
  apiKey: process.env.NEXT_PUBLIC_MICRO_CMS_API_KEY || ''
})
```

## getStaticPaths
microCMSで作成した記事のコンテンツIDをurlに使う動的ルーティングのページなため、`getStaticPaths`で静的に生成されるパスのリストを作成します。

https://nextjs.org/docs/api-reference/data-fetching/get-static-paths

```[id].tsx 
export const getStaticPaths = async () => {
  const data = await cmsClient.get({
    // microCMSで作成したAPIの「エンドポイント」の値です    
    endpoint: 'blog',
    // limitを設定しないとデフォルトで10件取得なので、任意のlimit数を入れておきます
    queries: { limit: 9999 }
  })

  const paths = data.contents.map(
    // コンテンツIDをURLに使用します
    (content: ArticleType) => `/blog/${content.id}`
  )
  return { paths, fallback: true }
}
```

- `endpoint`に入るのはmicroCMSで作成したAPIの`エンドポイント`の値です。
- `queries`の指定もできます。`limit`は何も指定しないと10件になってしまうので、任意の数を入れます。limitの上限はありませんが、レスポンスサイズが5MBを超えるとエラーになります。ここで使うのはpathに使うidだけなので、`fields`で取得する要素を限定するのも良さそうです。

https://document.microcms.io/content-api/get-list-contents#h9ce528688c

- `fallback`は `getStaticPaths`でパスが返されないときの挙動を指定します。
  - `false`：404ページになります。
  - `true`：404になりません。空のフォールバックページが生成され、後述の `getStaticPaths`に値がある場合にはそれを使ってページアクセス時にレンダリングするようにすることが可能です。
  - `blocking`：404になりません。trueのときにAPIからデータを取得する最中の表示をブロックします。
- ただしこのfallbackオプションは、`next export`で静的なHTMLを出力するようにしている場合には使用できません。

## getStaticProps
APIからデータを取得した値をpropsとして渡し、ビルド時にプリレンダリングします。

https://nextjs.org/docs/api-reference/data-fetching/get-static-props

```[id].tsx
export const getStaticProps: GetStaticProps<StaticProps> = async (
  context: GetStaticPropsContext
) => {
  // getStaticPathsで用意した[id].tsxのid部分が入っています
  const id = context.params?.id

  // microCMSのAPIを叩きます
  const data = await cmsClient
    .get({
      endpoint: 'blog',
      // ページ用に1件取得するのでcontentIdを指定します
      contentId: String(id)
    })
    .catch((err) => console.error(err))

  if (!data) {
    return {
      // データがないとき404ページになります
      notFound: true
    }
  }

  return {
    props: {
      data
    }
  }
}
```
- returnの`props`でpagesのコンポーネントにdataを渡すことができます。
- returnの`notFound`はtrueのとき404ページになります。
- `revalidate`はISR用です。

## [id].tsx

dataをpropsとして渡された残りのページ部分です。

```[id].tsx
import React from 'react'
import {
  NextPage,
  InferGetStaticPropsType,
  GetStaticPropsContext,
  GetStaticProps
} from 'next'
import { useRouter } from 'next/router'
import type { ArticleType } from '@/rtk/services/cms'
import { LoadingPage } from '@/components/loading'
import { cmsClient } from '@/lib/microcms/microcms'
import { DetailsArticles } from '@/components/details'

type Props = InferGetStaticPropsType<typeof getStaticProps>
type StaticProps = {
  data: ArticleType
}

const Article: NextPage<Props> = ({ data }) => {
  const router = useRouter()

  // フォールバック中であることをnext/routerのisFallbackで取得できるので、その間ローディング表示にできます
  if (router.isFallback) {
    return <LoadingPage />
  }

  return (
    // dataを使ってコンポーネントの表示ができます（色々省略しています）
    <DetailsArticles
      data={data}
    />
  )
}
export default React.memo(Article)
```
- 上の項目でしれっと書いていましたが、`getStaticProps`などで使っているtypescript用の型指定は色々用意してあるので適宜使います。
- `getStaticPaths`のfallbackオプションがtrueのときフォールバック中に`isFallback`がtrueになるのでローディング表示などに利用できます。

## まとめ
- SSGすることによってページが高速に表示できるようになりました。
- metaタグに入れる記事タイトルなどのデータを無事プリレンダリングするようになったので、GAでも正しく計測できるようになりました。
- SSGすると同じページでクライアントサイドでAPIを叩いてのプレビュー表示（URLパラメータにdraftkeyを入れるなどする）ができなくなるので、別の方法を検討する必要があります。Vercelを使用している場合はNext.jsのPreview Modeを使ったりできると思います（AWS Amplifyだとcookieとwebhookでの自動デプロイの兼ね合いで実現が難しく、諦めました）

https://nextjs.org/docs/advanced-features/preview-mode

https://blog.microcms.io/nextjs-preview-mode