お首が長いのよお首が長いのよ

チラシの裏よりお届けするソフトウェアエンジニアとして成長したい人のためのブログ

2021-08-24

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

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

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

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

md: title=md
1---
2path: "/path/to/the/file"
3date: "2021-mm-dd""
4title: "タイトル"
5categories: ["カテゴリ1", "カテゴリ2"]
6tags: ["GatsbyJS"]
7type: "post"
8---
9
10

コンポーネントを作成

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

js: title=src/components/categories.js
1import React from "react"
2import { Link } from "gatsby"
3import kebabCase from "lodash/kebabCase"
4
5const Categories = categories => (
6  <div class="categories">
7    {(categories || []).map(category => (
8      <Link to={`/categories/${kebabCase(category)}/`}>
9        <span class="post-category">{category}</span>
10      </Link>
11    ))}
12  </div>
13)
14
15export default Categories
16

一覧ページを作成

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

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

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

js: title=src/templates/CategoryList.js
1
2import React from "react"
3import { Link, graphql } from "gatsby"
4import Categories from "../components/catetgories"
5import Tags from "../components/tags"
6
7const CategoryList = ({ data, pageContext }) => {
8  const { category } = pageContext
9  return (
10      <h1>{category} の絞り込み結果</h1>
11      {data.allMarkdownRemark.edges.map(({ node }) => (
12        <article class="row">
13          <div class="row col-xs-12 col-sm-12 col-md-12 meta-container">
14                <h1>
15                  <Link to={node.fields.slug}>{node.frontmatter.title}</Link>
16                </h1>
17              <div class="row col-md-12 post-meta-data text-gi-med bg-gi-light">
18                <div class="row col-md-12">
19                  <small> {Categories(node.frontmatter.categories)}</small>
20                  <small> {Tags(node.frontmatter.tags)}</small>
21                </div>
22              </div>
23          </div>
24        </article>
25      ))}
26  )
27}
28
29export const query = graphql`
30  query($category: String) {
31    allMarkdownRemark(
32      sort: { fields: [frontmatter___date], order: DESC }
33      filter: { frontmatter: { categories: { in: [$category] } } }
34      limit: 2000
35    ) {
36      totalCount
37      edges {
38        node {
39          frontmatter {
40            title
41            categories
42            tags
43          }
44          fields {
45            slug
46          }
47        }
48      }
49    }
50  }
51`
52export default CategoryList
53
54

JSX

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

GraphQL クエリ

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

gatsby-node.js を修正

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

js: title=gatsby-node.js
1
2... // 省略
3
4const result = await graphql(`
5	{
6		allMarkdownRemark(
7			sort: { order: DESC, fields: [frontmatter___date] }
8			limit: 1000
9		) {
10			nodes {
11				id
12				fields {
13					slug
14				}
15			}
16		}
17		tagsGroup: allMarkdownRemark(limit: 1000) {
18			group(field: frontmatter___tags) {
19				fieldValue
20			}
21		}
22		categoriesGroup: allMarkdownRemark(limit: 1000) {
23			group(field: frontmatter___categories) {
24				fieldValue
25			}
26		}
27	}
28`)
29
30... // 省略
31
32// タグ別記事一覧を生成する
33const tags = result.data.tagsGroup.group
34tags.forEach(tag => {
35	createPage({
36		path: `/tags/${kebabCase(tag.fieldValue)}/`,
37		component: path.resolve(`./src/templates/TagList.js`),
38		context: {
39			tag: tag.fieldValue,
40		},
41	})
42})
43
44// カテゴリー別記事一覧を生成する
45const categories = result.data.categoriesGroup.group
46categories.forEach(category => {
47	createPage({
48		path: `/categories/${kebabCase(category.fieldValue)}/`,
49		component: path.resolve(`./src/templates/CategoryList.js`),
50		context: {
51			category: category.fieldValue,
52		},
53	})
54})
55
56

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

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

aliasName: allMarkdownRemark() { someItem } 

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

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

やってみて思ったこと

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

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

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

/ 以上

よかったらシェアしてください!