GatsbyJSにカテゴリ一覧、タグ一覧を追加した

重い腰を上げてタグ一覧、カテゴリ一覧を実装した。実は元々タグ、カテゴリといったメタ情報はこのブログにあったが、一覧ページを作成するところで手を止まってしまっていた。GatsbyJSを思い出すよいトレーニングになると思ったので、さくっと実装してみる。

記事ファイルにタグ、カテゴリを追加

このブログはMarkdownで書いているので、先頭のメタデータにタグとカテゴリーを文字列配列で書く。

md
---
path: "/path/to/the/file"
date: "2021-mm-dd""
title: "タイトル"
categories: ["カテゴリ1", "カテゴリ2"]
tags: ["GatsbyJS"]
type: "post"
---

コンポーネントを作成

記事一覧等に埋め込むための src/components/categories.js, src/components/tags.js コンポーネントを作成する。 src/components/tags.jscategories.js とほとんど一緒なので割愛。

src/components/categories.js
import React from "react"
import { Link } from "gatsby"
import kebabCase from "lodash/kebabCase"

const Categories = categories => (
  <div class="categories">
    {(categories || []).map(category => (
      <Link to={`/categories/${kebabCase(category)}/`}>
        <span class="post-category">{category}</span>
      </Link>
    ))}
  </div>
)

export default Categories

一覧ページを作成

もととなるテンプレートファイルを作成する。

src/templates/CategoryList.jssrc/templates/TagList.js を作成した。

TagListはクエリ含めてCategoryListとほとんど同じなので割愛。

src/templates/CategoryList.js
import React from "react"
import { Link, graphql } from "gatsby"
import Categories from "../components/catetgories"
import Tags from "../components/tags"

const CategoryList = ({ data, pageContext }) => {
  const { category } = pageContext
  return (
      <h1>{category} の絞り込み結果</h1>
      {data.allMarkdownRemark.edges.map(({ node }) => (
        <article class="row">
          <div class="row col-xs-12 col-sm-12 col-md-12 meta-container">
                <h1>
                  <Link to={node.fields.slug}>{node.frontmatter.title}</Link>
                </h1>
              <div class="row col-md-12 post-meta-data text-gi-med bg-gi-light">
                <div class="row col-md-12">
                  <small> {Categories(node.frontmatter.categories)}</small>
                  <small> {Tags(node.frontmatter.tags)}</small>
                </div>
              </div>
          </div>
        </article>
      ))}
  )
}

export const query = graphql`
  query($category: String) {
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { categories: { in: [$category] } } }
      limit: 2000
    ) {
      totalCount
      edges {
        node {
          frontmatter {
            title
            categories
            tags
          }
          fields {
            slug
          }
        }
      }
    }
  }
`
export default CategoryList

JSX

前述で作成した src/components/categories.js src/components/tags.js を読み込む。

GraphQL クエリ

filtercategories の中から $category をもつ記事のみを抽出しているのがポイント。

gatsby-node.js を修正

記事にカテゴリ、タグを追記し、一覧ページの作成も完了したら、 gatsby-node.js ファイルを修正してビルド時にカテゴリ一覧ページ、タグ一覧ページを作成するように設定する。

gatsby-node.js
... // 省略

const result = await graphql(`
	{
		allMarkdownRemark(
			sort: { order: DESC, fields: [frontmatter___date] }
			limit: 1000
		) {
			nodes {
				id
				fields {
					slug
				}
			}
		}
		tagsGroup: allMarkdownRemark(limit: 1000) {
			group(field: frontmatter___tags) {
				fieldValue
			}
		}
		categoriesGroup: allMarkdownRemark(limit: 1000) {
			group(field: frontmatter___categories) {
				fieldValue
			}
		}
	}
`)

... // 省略

// タグ別記事一覧を生成する
const tags = result.data.tagsGroup.group
tags.forEach(tag => {
	createPage({
		path: `/tags/${kebabCase(tag.fieldValue)}/`,
		component: path.resolve(`./src/templates/TagList.js`),
		context: {
			tag: tag.fieldValue,
		},
	})
})

// カテゴリー別記事一覧を生成する
const categories = result.data.categoriesGroup.group
categories.forEach(category => {
	createPage({
		path: `/categories/${kebabCase(category.fieldValue)}/`,
		component: path.resolve(`./src/templates/CategoryList.js`),
		context: {
			category: category.fieldValue,
		},
	})
})

GraphQLクエリでは、後半に tagsGroup: ...categoriesGroup:... からなるクエリを追加している。

元々あった記事一覧を取得するクエリと比べ、何をしているのか最初はわからなかったが、普通に書こうとすると allMarkdownRemark() と干渉してしまってクエリが書けないため、下記のようにすることで名前空間を分けることができるみたい。

aliasName: allMarkdownRemark() { someItem } 

この場合、 allMarkdownRemark().someItem ではなく aliasName.someItemのように書けるとのこと。

ビルドして問題なく動作すれば完了である。

やってみて思ったこと

GraphQL、昔触ったときも今触ったときも違和感しかないが、SQLと違って取得した結果をJSのオブジェクトでそのまま取れるのはだいぶ楽かもしれない。速いし。

上記のクエリもまだ冗長な書き方をしている気がするので、複雑になるにつれて書き方を改めたほうがよさそう。

そろそろ自動目次作成や、SNSシェアボタンが欲しくなってきたのでその辺も調べて実装してみる。

/ 以上