2021/10/02

Next.js + microCMS で自作ブログを構築しました

こんにちは、ばんがです。

ブログを始めるにあたって、自分で作った方がモチベがあがるかな?と思って実装してみました。
タイトルにもある通り今回はNext.js(SSG) + microCMS(+ vercel)を利用しています。
いわゆるJamstackと言われるものにしてみました。

Next.js / microCMSを選んだ理由

途中で飽きてやめないようにできるだけサクッと作ってしまいたいたいと考えていたので、できるだけ手間がかからないものを選びました。

Next.js(React)を選んだのは、

  • 業務で軽く触っているので基本的なことはわかる
  • ブログなので静的なサイトで良い & SSGは触ったことがないので勉強したい

という理由からです。

バックエンドも手間をかけたくなかったので、自分で実装するのではなくCMSを利用することにしました。
その中でもmicroCMSは日本製でお手軽だったので使ってみることにしました。
日本語のサービスなのでわかりやすかった、というのもあります笑

ブログの機能、ページについて

現状は投稿一覧、投稿詳細のページ(+ About)という最低限のページしかないです。
機能と言えるほどのものではないですが、

  • 記事へのタグ(カテゴリー)付け
  • 投稿一覧の無限スクロール

はできるようにしました。

以下の機能は今後追加していきたいなと考えています。

  • タグ(カテゴリー)やキーワードでの記事検索
  • SEO対策やGAの導入


microCMSで記事APIを作る

microCMSでは画面ぽちぽちですぐAPIが作れます。
以下のように記事を取得するAPIのスキーマを定義します。

このスキーマにしたがって記事内容を登録すると、こんな感じでAPIレスポンスが返ってきます。
これは記事一覧のAPIですが、自動的に1つの記事を取得するAPIも作成されます。(記事IDを指定)

{
    "contents": [
        {
            "id": "XXXXXXXX",
            "createdAt": "2021-09-30T15:42:38.104Z",
            "updatedAt": "2021-10-01T04:45:58.531Z",
            "publishedAt": "2021-10-01T03:10:22.718Z",
            "revisedAt": "2021-10-01T04:45:58.531Z",
            "title": "ブログを始めました",
            "body": "<p>こんにちは。<br>初めましての人は初めまして。.....",
            "category": [
                "雑談"
            ]
        }
    ],
    "totalCount": 1,
    "offset": 0,
    "limit": 10
}


Next.jsからAPIを叩く

microCMSはSDKが提供されているのでそれを使います。
SDKの設定方法についてはこちらを参照。

今回はSSGで静的サイトとして構築するので、ビルド時にレンダリングが行われます。
そのため、ビルド時にAPIから記事データを取ってきてやる必要があります。

そこで、Next.jsがSSG用に提供しているgetStaticPropsgetStaticPathsというメソッドを使います。

getStaticProps

このメソッドはビルド時に実行され、レンダリングに必要なデータをpropsを通じてコンポーネントに渡すことができます。
SSGでは、このメソッド内でAPIを叩いて記事データを取得する処理を書いておく必要があります。

例えば記事一覧ページの場合以下な実装になります。
getStaticProps内でmicroCMSにある記事データの一覧を取得し、propsにセットしています。

const Home: React.FC<Props> = ({articles}) => {
  // 記事データごとに表示する
  {articles.map((article) => (
    ...)
  }
}

export const getStaticProps = async () => {
  let articles: ArticleType[] = [];
  const limit = 10;
  let offset = 0;

  // client = microCMS SDK
  const data: ArticleListType = await client.get(
    {
      endpoint: "articles",
      queries: {limit: limit, offset: offset}
    }
  );

  return {
    props: { articles: articles }
  }
}


参考:https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation

getStaticPaths

ブログなので、投稿一覧だけではなく1つ1つの記事のページも作る必要があります。
記事IDは外部データ(microCMSにある記事データ)に依存しており、これらのページもビルド時にレンダリングするので事前に必要な記事ページのパスを生成する必要があります。
そこで使うのがgetStaticPathsです。

動的ルーティングする必要があるページのコンポーネントにgetStaticPathsメソッドを追加します。
ここではmicroCMSにある記事データを取得し、その記事IDごとのページのパスを生成して返しています。

export const getStaticPaths = async () => {
  const data: ArticleListType = await client.get({endpoint: "articles"})

  const paths = data.contents.map((content) => `/articles/${content.id}`)
  return {paths, fallback: false};
}


getStaticPathsgetStaticProps同様ビルド時に実行され、存在する記事IDから必要になる記事ページのパスが生成されます。

無限スクロールについて

SSGで無限スクロールってどうなんだ...?って気もしましたが実装してみたかったので。

無限スクロールはreact-infinite-scrollerというライブラリを使用しました。
https://github.com/danbovey/react-infinite-scroller

このライブラリではwindowや指定したDOMの最下部に到達するたびに次のデータを読み込むコールバックが呼ばれます。
SPAで作るのであれば、このコールバック内でmicroCMS APIを呼び出して次のデータを取得すれば良いのですが、SSGではもちろんできません。

SSGではビルド時にレンダリングが行われるので、getStaticPropsで全データを取得しておく必要があります。
以下のようにして実装してみました。

const Home: React.FC<Props> = ({articles}) => {
  const itemsPerScroll = 10;
  const [hasMore, setHasMore] = useState(articles.length > 10);
  const [items, setItems] = useState<ArticleType[]>(articles.slice(0, itemsPerScroll))

  const loadMore = (page: number) => {
    const startIdx = page * itemsPerScroll;
    const endIdx = startIdx + itemsPerScroll;
    setTimeout(() => {
      setItems(items.concat(articles.slice(startIdx, endIdx)))
      if (endIdx > articles.length) setHasMore(false);
    }, 500)
  }

  return (
    <div>
      <InfiniteScroll
        pageStart={0}
        initialLoad={false}
        loadMore={loadMore}
        hasMore={hasMore}
        loader={<Loader key="loader"><Loading/></Loader>}
      >
        {
          <ul>
            {items.map((item) => (
              // 記事一覧を表示する処理
            ))}
          </ul>
        }
      </InfiniteScroll>
    </div>
  )
}

export default Home;

export const getStaticProps = async () => {
  let articles: ArticleType[] = [];
  const limit = 10;
  let offset = 0;

  while (true) {
    const data: ArticleListType = await client.get(
      {
        endpoint: "articles",
        queries: {limit: limit, offset: offset}
      }
    );

    if (data.contents.length === 0) break;

    articles = articles.concat(data.contents);
    offset += limit;
  }

  return {
    props: { articles: articles }
  }
}


データ自体は事前に全取得しておきコンポーネントに渡します。
コンポーネントでは一度のローディングで表示したいデータ数を制限し、ページ最下部に到達してloadMoreコールバックが呼ばれた時に、次のデータセットを読み込んでいます。

所感

Next.jsでの静的サイト生成はgetStaticPropsgetStaticPathsという便利なメソッドが提供されているため非常に簡単に実装できました。
公式ドキュメントもすごくわかりやすく書かれているので、つまづくところはあまりなかったです。

microCMSも、普通のブログを作るだけなら画面ぽちぽちでサクッとAPIを作れるしUIもわかりやすいし、非常に便利だと思います。
強いて言えばMarkdownでもっと記事が書きやすくなればな...というくらいです。

ひとまず最小限の機能で作ったので、今後は検索機能やSEO対策など拡張していきたいと思います。

それではまた。