2020, Nov 01

Gatsby.jsサイトにCategoryページを導入する

Gatsby.jsサイトにCategory(カテゴリ)ページを導入する方法をまとめます。(マークダウン使用)

マークダウンに記事を書いている前提で、カテゴリページを導入したいと思ったのでメモです。手順は以下の通りです。

  • マークダウン記事に category を追加
  • /category/*** ページとなるテンプレート作成
  • gatsby-node.js でページ生成

実際の手順は以下です。

マークダウン記事に category を追加

---
title: Gatsby.jsサイトにCategoryページを導入する
date: 2020-11-01 00:00:00 -0900
category: tech
tags: [gatsbyjs]
---

# 以下記事本文

techというカテゴリを追加したいとします。上記のようにcategory: techの 1 行を追加しました。

移行、マークダウンの記事にカテゴリを追加したい場合はこのように記載していきます。

テンプレートの作成

# /src/templates/category.js

import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/layout'
import PostCard from '../components/postCard'

const Category = ({ pageContext, data }) => {
  const { category } = pageContext
  const { edges, totalCount } = data.allMarkdownRemark
  const catHeader = `${category}」カテゴリの記事(${totalCount}件)`

  return (
    <Layout>
      <div className="md:flex">
        <div className="w-full md:w-3/4">
          <h1 className="pl-3 md:pl-0 text-lg mb-6">{catHeader}</h1>
          <div className="">
            {edges.map(({ node }) => {
              return <PostCard node={node} />
            })}
          </div>
        </div>
      </div>
    </Layout>
  )
}

export default Category

export const pageQuery = graphql`
  query($category: String) {
    allMarkdownRemark(
      limit: 2000
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { category: { eq: $category } } }
    ) {
      totalCount
      edges {
        node {
          excerpt(pruneLength: 100)
          fields {
            slug
          }
          timeToRead
          frontmatter {
            title
            date(formatString: "MMMM DD, YYYY")
            category
          }
        }
      }
    }
  }
`

pageContextはこの後、gatsby-node.jsから引数として渡されます。

また、graphql の中の$categoryも同様に gatsby-node.js から渡されてくる引数です。

PostCardコンポーネントは各自node配下のデータを渡してコンポーネントを作成しているだけです。

gatsby-node.js でページ生成

最後に gatsby-node.js 側でカテゴリページを生成します。

# gatsby-node.js
const path = require(`path`)
const _ = require('lodash')
const { createFilePath } = require(`gatsby-source-filesystem`)

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

  const blogPost = path.resolve(`./src/templates/blog-post.js`) //こちらは個別記事を生成する部分で今回無関係
  const catTemplate = path.resolve(`./src/templates/category.js`) //ここで先ほど作ったテンプレートを読み込む

  return graphql(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                slug
              }
              frontmatter {
                title
                category // categoryを追加
              }
            }
          }
        }
      }
    `
  ).then(result => {
    if (result.errors) {
      throw result.errors
    }

    // Create blog posts pages. 個別記事生成 今回無関係
    const posts = result.data.allMarkdownRemark.edges

    posts.forEach((post, index) => {
      const previous = index === posts.length - 1 ? null : posts[index + 1].node
      const next = index === 0 ? null : posts[index - 1].node

      createPage({
        path: post.node.fields.slug,
        component: blogPost,
        context: {
          slug: post.node.fields.slug,
          previous,
          next,
        },
      })
    })

    // Create blog post list pages 記事リスト生成 今回無関係
    const postsPerPage = 6
    const numPages = Math.ceil(posts.length / postsPerPage)

    Array.from({ length: numPages }).forEach((_, i) => {
      createPage({
        path: i === 0 ? `/` : `/${i + 1}`,
        component: path.resolve('./src/templates/blog-list.js'),
        context: {
          limit: postsPerPage,
          skip: i * postsPerPage,
          numPages,
          currentPage: i + 1,
        },
      })
    })

    // この部分を今回追加
    // create Category pages
    let categories = []
    _.each(posts, edge => {
      if (_.get(edge, 'node.frontmatter.category')) {
        categories.push(edge.node.frontmatter.category)
      }
    })
    // Eliminate duplicate categorya
    categories = _.uniq(categories)
    // Make category pages
    categories.forEach(category => {
      createPage({
        path: `/category/${_.kebabCase(category)}/`,
        component: catTemplate,
        context: {
          category,
        },
      })
    })
    // 今回追加ここまで
  })
}

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

これで/category/tech配下にカテゴリに属する記事一覧のページが生成されているはずです。

以上

Tag 機能は元々いろんなスターターに組み込まれていましたが、カテゴリはパッと見つからなかったのでまとめてみました。

誰かの参考になれば幸いです。