📝

Cloudflare Pages x microCMS x GatsbyJSでブログを作ってみる

2022/04/25に公開

知り合いが「Wordpressでブログ云々...」と話しているのを聞いて、ふと「いまどきブログを作るなら、どんな構成がいいんだろう?」と思い、調べて作ってみることにしました。

https://github.com/momonoki1990/gatsby-microcms-starter-blog

CMS

https://usomitainikagayakumachi.tokyo/2020-05-19_友人のブログを構築するのに最適なheadless-cms選び/

https://anken-hyouban.com/blog/2021/12/17/contentful/

今回は日本語対応でAPIベースのmicroCMSにすることにしました。

最初はホスティングサービスであるNetlifyが提供しているNetlify CMSが一番簡単にすむかなと思ったのですが、エディタの日本語入力が怪しいという情報が散見されたのと、ホスティングサービスとしてもVercelやCloudflare Pagesの方がパフォーマンスがよさそうだったのでやめました。

他には、最もメジャーで機能が豊富そうなContentfulと、標準でGraphCMSが気になりました。

本格的にブログを運用する場合は、機能面でもいろいろと違うみたいなので、CMSだけ触ってみて比較してみた方が良さそうですね。

ホスティングサービス

https://zenn.dev/catnose99/scraps/6780379210136f

Zennの開発者でもあるcatnoseさんが、こちらの記事でVercel, Netlify, Cloudflare Pagesを詳細に比較されています。

他の方も書かれていましたが、Netlifyはページの表示速度が体感でわかるくらい遅いというのが大きなマイナスポイントですね。

パフォーマンス的にはVercelとCloudflare Pagesが並ぶところですが、Vercelは無料プランだと商用利用が許可されておらず、広告を貼るだけでもダメなようだったので、今回はCloudflare Pagesを選択しました。

https://pages.cloudflare.com/

Cloudflare Pagesの料金表

サイト数・Bandwidth・リクエスト数のすべてが無制限ということで、最強ですねw

フレームワーク

Next.jsかGatsbyJSかで迷い、GatsbyJSにしました。

GatsbyJSは使用した経験が無かったのですが、テンプレートやプラグインというGatsbyJS専用のnpmモジュール群が充実していて、「静的ページをReactベースでサクッと作るならGatsbyJS」という印象を持ったので、GatsbyJSを選択しました(どうでもいいけど、「ギャッツビー」って音がカッコイイw)

実際触ってみると、GraphQLインターフェースが徹底されていて、例えば、設定ファイルに記載したメタデータやプラグインが提供するデータにGraphQLでアクセスすることができます。

microCMSの公式プラグインも用意されていて、microCMS自体はREST APIを提供していますが、プラグインにAPIキーなどの情報を設定することで、GraphQLでデータにアクセスできるようになります。

https://reffect.co.jp/react/gatsby-basic-tutorial-for-beginners

GatsbyJSの仕組みに関しては、こちらのブログで連載されているGatybyJS関係の記事が素晴らしくわかりやすかったので、おすすめです。

作ってみる

ローカルで表示する

主な参考はこちら。

https://document.microcms.io/tutorial/gatsby/gatsby-getting-started

https://blog.microcms.io/gatsby-microcms-media/

使用するStarterはこちら。

https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog

npx gatsby new gatsby-microcms-starter-blog https://github.com/gatsbyjs/gatsby-starter-blog

...
Your new Gatsby site has been successfully bootstrapped. Start developing it by running:

  cd gatsby-microcms-starter-blog
  gatsby develop

ローカルで表示してみます。

cd gatsby-microcms-starter-blog
gatsby develop

Blog Starterを立ち上げ

Starterがローカルで表示されました。

APIの作成

次に、microCMSでAPIを作成していきます。

新規登録から進んでいくと、「サービスの作成(の情報を入力)」という画面が表示されます。

サービスの作成(の情報を入力)

サービスというのは、テキストや画像など任意の情報を提供するAPIをまとめたものです。

ここで料金プランを確認して驚いたのですが、無料プランであるHobbyでは、1サービスにつきAPIは最大3個までという制限はあるものの、サービスの数に関しては制限がないんですね。

料金プラン

すでにサービスを1つ作っている私のアカウントでも試してみたら、問題なくサービスがもう一つ作れるようでした。

サービスIDは管理画面やAPIのエンドポイントのサブドメインになります。

次にAPIの情報を入力する画面が出てくるのでこちらも入力していきます。

APIの基本情報

APIの型を選択します。

APIの型

ブログの記事など、複数個データを提供するものはリスト型、ブログのタイトルや設定情報など、単体のデータを提供するものはオブジェクト型を選択します。

今回はブログ記事なので、リスト型を選択しました。

次にAPIスキーマを定義する画面が表示されます。

ここでは、APIが提供するデータを設定していきます。

APIスキーマ

こちらはAPIを作成後、「API設定」→「APIスキーマ」から表示したAPIの編集画面ですが、いつでもスキーマを編集することができます。

APIを作成すると、「コンテンツがありません」と表示されるので、設定したスキーマに従ってデータを入力し、コンテンツを作成します。

コンテンツがありません

コンテンツ編集

コンテンツ一覧

これでAPIの作成は完了です。

上のコンテンツ一覧画面の右上ある「APIプレビュー」をクリックすると、APIにアクセスするための情報が確認できます。

X-MICROCMS-API-KEY、サービスID(.microcmsの前のサブドメインの値)、エンドポイント(blogなど)をこの後で環境変数に設定します。

microCMSからデータを取得する

ローカルで作成したGatsbyJSのStarterで、microCMSで作成したAPIからデータを取得できるようにしていきます。

https://www.gatsbyjs.com/plugins/gatsby-source-microcms/

microCMS専用のプラグインであるgatsby-source-microcmsと、環境変数を設定するためにdotenvをインストールします。

npm install gatsby-source-microcms dotenv

ルートディレクトリに.envを作成して、先ほど確認したAPIにアクセスするための情報を入力します。

MICROCMS_API_KEY={X-MICROCMS-API-KEYの値}
MICROCMS_SERVICE_ID={Service ID}
MICROCMS_ENDPOINT={API Endpoint}

次に、gatby-config.jsdotenvを有効にして、プラグインに関する設定を記載します。

gatby-config.js
require("dotenv").config()

module.exports = {
	...
	plugins: [
		...,
		// For microCMS
		{
      resolve: "gatsby-source-microcms",
      options: {
        apiKey: process.env.MICROCMS_API_KEY,
        serviceId: process.env.MICROCMS_SERVICE_ID,
        apis: [
          {
            endpoint: process.env.MICROCMS_ENDPOINT,
            format: "list",
          },
        ],
      },
    },
	]
}

gatsby developを再度実行して、http://localhost:8000/___graphqlをブラウザで開いてPlaygroundを確認すると、

Playground

クエリルート一覧にallMicrocmsBlogmicrocmsBlogが追加されているのがわかります。

Blogの部分は先ほどgatsby-config.jsで設定したendpointの値の先頭を大文字にしたものです。

ちなみに、format: "object"としてしまうと、nodeの型が変わってしまうため、この後で、nodes { blogId }などとクエリしようとすると、Cannot query field "blogId" on type "MicrocmsBlog".などとエラーが出てしまいます(ハマりました...)

次に、一覧画面用のページファイルであるsrc/pages/index.jsを修正して、記事一覧を表示できるようにします。

index.js
index.js
import * as React from "react"
import { Link, graphql } from "gatsby"

import Bio from "../components/bio"
import Layout from "../components/layout"
import Seo from "../components/seo"

const BlogIndex = ({ data, location }) => {
  const siteTitle = data.site.siteMetadata?.title || `Title`
  const posts = data.allMicrocmsBlog.nodes

  if (posts.length === 0) {
    return (
      <Layout location={location} title={siteTitle}>
        <Seo title="All posts" />
        <Bio />
        <p>
          No blog posts found. Add markdown posts to "content/blog" (or the
          directory you specified for the "gatsby-source-filesystem" plugin in
          gatsby-config.js).
        </p>
      </Layout>
    )
  }

  return (
    <Layout location={location} title={siteTitle}>
      <Seo title="All posts" />
      <Bio />
      <ol style={{ listStyle: `none` }}>
        {posts.map(post => {
          const title = post.title || "Title"

          return (
            <li key={post.blogId}>
              <article
                className="post-list-item"
                itemScope
                itemType="http://schema.org/Article"
              >
                <header>
                  <h2>
                    <Link to={post.blogId} itemProp="url">
                      <span itemProp="headline">{title}</span>
                    </Link>
                  </h2>
                  <small>{post.createdAt}</small>
                </header>
                <section>
                  <p
                    dangerouslySetInnerHTML={{
                      __html: post.description || "default body",
                    }}
                    itemProp="description"
                  />
                </section>
              </article>
            </li>
          )
        })}
      </ol>
    </Layout>
  )
}

export default BlogIndex

export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
      }
    }
    allMicrocmsBlog(sort: { fields: [createdAt], order: DESC }) {
      nodes {
        blogId
        title
        description
        createdAt
      }
    }
  }
`

http://localhost:8000/にアクセスして確認してみると、

一覧ページ

データが取得できているのがわかります。

続いて、gatsby-node.jsを編集します。

gatsby-node.jsに記載したコードはビルド時に実行され、たとえば、createPageでURLのパスやコンポーネント、渡すデータなどを設定してページを作ることができます。

https://www.gatsbyjs.com/docs/creating-and-modifying-pages/#creating-pages-in-gatsby-nodejs

gatsby-node.js

...
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions

// Define a template for blog post
const blogPost = path.resolve(./src/templates/blog-post.js)

// Get all markdown blog posts sorted by date
const result = await graphql(
{ allMicrocmsBlog(sort: { fields: [createdAt], order: DESC }) { edges { node { id blogId createdAt } previous { id } next { id } } } }
)

if (result.errors) {
reporter.panicOnBuild(
There was an error loading your blog posts,
result.errors
)
return
}

const edges = result.data.allMicrocmsBlog.edges

// Create blog posts pages
// But only if there's at least one markdown file found at "content/blog" (defined in gatsby-config.js)
// context is available in the template as a prop and as a variable in GraphQL

if (edges.length > 0) {
edges.forEach(({ node: post, previous, next }, index) => {
// const previousPostId = index === 0 ? null : edges[index - 1].id
const previousPostId = previous ? previous.id : null
// const nextPostId =
// index === edges.length - 1 ? null : edges[index + 1].id
const nextPostId = next ? next.id : null
createPage({
// ページのURLのパス
path: post.blogId,
// ページコンポーネント
component: blogPost,
// ページコンポーネントに渡すデータ
context: {
id: post.id,
previousPostId,
nextPostId,
},
})
})
}
}
...

次に、ページコンポーネントに指定したsrc/templates/blog-post.jsで各ページの内容を記載します。

blog-post.js
blog-post.js
import * as React from "react"
import { Link, graphql } from "gatsby"

import Bio from "../components/bio"
import Layout from "../components/layout"
import Seo from "../components/seo"

const BlogPostTemplate = ({ data, location }) => {
  console.log(data)
  const post = data.microcmsBlog
  const siteTitle = data.site.siteMetadata?.title || `Title`
  const { previous, next } = data

  return (
    <Layout location={location} title={siteTitle}>
      <Seo
        title={post.title}
        description={post.description || "default description"}
      />
      <article
        className="blog-post"
        itemScope
        itemType="http://schema.org/Article"
      >
        <header>
          <h1 itemProp="headline">{post.title}</h1>
          <p>{post.createdAt}</p>
        </header>
        <section
          dangerouslySetInnerHTML={{ __html: post.bodyHtml }}
          itemProp="articleBody"
        />
        <hr />
        <footer>
          <Bio />
        </footer>
      </article>
      <nav className="blog-post-nav">
        <ul
          style={{
            display: `flex`,
            flexWrap: `wrap`,
            justifyContent: `space-between`,
            listStyle: `none`,
            padding: 0,
          }}
        >
          <li>
            {previous && (
              <Link to={previous.id} rel="prev">{previous.title}
              </Link>
            )}
          </li>
          <li>
            {next && (
              <Link to={next.id} rel="next">
                {next.title}</Link>
            )}
          </li>
        </ul>
      </nav>
    </Layout>
  )
}

export default BlogPostTemplate

// `gatsby-node.js`の`createPage()`で`context`として設定した値を引数として使用できる
export const pageQuery = graphql`
  query BlogPostById(
    $id: String!
    $previousPostId: String
    $nextPostId: String
  ) {
    site {
      siteMetadata {
        title
      }
    }
    microcmsBlog(id: { eq: $id }) {
      id
      title
      bodyHtml
      createdAt
    }
    previous: microcmsBlog(id: { eq: $previousPostId }) {
      id
      title
    }
    next: microcmsBlog(id: { eq: $nextPostId }) {
      id
      title
    }
  }
`

一覧ページから、個別の記事へのリンクをクリックすると、

記事詳細ページ

記事詳細ページにmicroCMSで設定したコンテンツが表示されているのがわかります。

Cloudflare Pagesへのデプロイ

https://pages.cloudflare.com/

Create your project

公式からアカウントを新規登録し、連携するGitHubリポジトリや環境変数などを設定すると、

あっという間にビルドとデプロイが始まります。

17:00:40.303	 ERROR
17:00:40.303	
17:00:40.303	Gatsby requires Node.js 14.15.0 or higher (you have v12.18.0).
17:00:40.303	Upgrade Node to the latest stable release: https://gatsby.dev/upgrading-node-js

nodeのバージョンがデフォルトでv12.18.0なっているらしく、エラーが出るので、環境変数に NODE_VERSIONを追加します(私は16.13.2を指定しました)

ビルド成功

ビルドが成功したので、Visit Siteというリンクをクリックすると、

デプロイ成功

デプロイが成功していることがわかります🎉

とりあえず、最小の設定でCloudflare Pages x microCMS x GatsbyJSでブログを作ってみました。

独自ドメインやデプロイ・microCMSでのプレビューの設定やTypeScript化など、まだまだやらなければならないことはありますが、一旦ここまでにしようと思います。

Discussion