Gatsbyにタグ機能、カテゴリ機能をつける（基礎編）

<head>

<link rel="canonical" href="https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags" />

</head>

# はじめに

> 本記事は https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags のクロスポスト記事になります。

この記事は[Gatsby](https://www.gatsbyjs.org/)というヘッドレスCMS技術で構成されています。今回は「エンジニア初心者でもできる」を前提に以下の構成で記事を作成していこうと思います。

* [Gatsby始めるまで](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog/)
* [GatsbyにShare機能、OGPタグをつける](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-share/)
* [タグ機能、カテゴリ機能をつける（基礎編）](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags)
* [タグ機能、カテゴリ機能をつける（応用編）](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags-application)
* [GatsbyにTableOfContents（目次）をつける](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-tableofcontent)
* [DarkModeをつける](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-darkmode)

# 内容

今回はWordPressのタグ機能、カテゴリ機能に当たる部分を実装していきます。標準で実装されてないの？！って突っ込みはあるかと思いますが、そうです。標準のテンプレートでは実装されておりません。なので実装するよりも最初からあるテンプレートを選んだほうが良いです。

[テンプレートのおすすめはこちら](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog/#%E3%81%BB%E3%81%BC%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E4%BD%BF%E3%81%88%E3%82%8B%E3%81%A8%E6%80%9D%E3%81%86template-3%E9%81%B8)

## 実装する前に

https://www.gatsbyjs.org/docs/adding-tags-and-categories-to-blog-posts/

まずこちらの記事を読みましょう。GatsbyJS本家でタグの付け方について記載されています。

## 手順

1. **markdown**ファイルにタグを追加する
2. GraphQLクエリを作成し、全てのタグを取得する
3. タグページテンプレートを作成する（**/tag/{tag}**）
4. 作成したテンプレートを使用して、gatsby-node.jsでページをレンダリングする
5. すべてのタグのリストを表示するタグインデックスページを作成する（**/tags**）

### 1.**markdown**ファイルにタグを追加する

タグがない場合のマークダウンファイル。ない場合追加する必要があります。

```markdown
---
title: "A Trip To the Zoo"
---

I went to the zoo today. It was terrible.
```

**tags**という名前で項目を追加しました。タグは複数個設定される想定なので、配列として定義します。他にも、文字列、数値が設定できます。（カテゴリも一緒で単一のため文字列として設定します。） 

```markdown
---
title: "A Trip To the Zoo"
tags: ["animals", "Chicago", "zoos"]
---

I went to the zoo today. It was terrible.
```

ローカル環境で**gatsby develop**が実行されている場合は、再起動すると、Gatsbyが新しい項目を取得できるようになります。

### 2. GraphQLクエリを作成し、全てのタグを取得する

GraphQLを確認するためにはローカルで**gatsby develop**を実行し、**http://localhost:8000/___graphql**にアクセスします。

下のように画面が見えるはずです。

![](https://ucarecdn.com/6842349f-7111-4172-a818-0256145577d5/)

画面が表示されたら下のクエリーを入力してみましょう。

```graphql
{
  allMarkdownRemark {
    group(field: frontmatter___tags) {
      tag: fieldValue
      totalCount
    }
  }
}
```

タグ一覧が取得できるはずです。

![](https://ucarecdn.com/12797c56-4154-491a-aeb1-b76dae963180/)

ちなみに**group()**はSQLの**GroupBy**と同じような意味合いです。ここではタグ項目でグルーピングしています。

### 3. タグページテンプレートを作成する（**/tag/{tag}**）

ディレクトリは**src/template/tags.js**などの配置にしましょう。

```js
import React from "react"
import PropTypes from "prop-types"

// Components
import { Link, graphql } from "gatsby"

const Tags = ({ pageContext, data }) => {
  const { tag } = pageContext
  const { edges, totalCount } = data.allMarkdownRemark
  const tagHeader = `${totalCount} post${
    totalCount === 1 ? "" : "s"
  } tagged with "${tag}"`

  return (
    <div>
      <h1>{tagHeader}</h1>
      <ul>
        {edges.map(({ node }) => {
          const { slug } = node.fields
          const { title } = node.frontmatter
          return (
            <li key={slug}>
              <Link to={slug}>{title}</Link>
            </li>
          )
        })}
      </ul>
      {/*
              This links to a page that does not yet exist.
              You'll come back to it!
            */}
      <Link to="/tags">All tags</Link>
    </div>
  )
}

Tags.propTypes = {
  pageContext: PropTypes.shape({
    tag: PropTypes.string.isRequired,
  }),
  data: PropTypes.shape({
    allMarkdownRemark: PropTypes.shape({
      totalCount: PropTypes.number.isRequired,
      edges: PropTypes.arrayOf(
        PropTypes.shape({
          node: PropTypes.shape({
            frontmatter: PropTypes.shape({
              title: PropTypes.string.isRequired,
            }),
            fields: PropTypes.shape({
              slug: PropTypes.string.isRequired,
            }),
          }),
        }).isRequired
      ),
    }),
  }),
}

export default Tags

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

> 補足

基本的に**src/template/**の配下のJSファイルはGraphQLがセットになっている構成が望ましいです。（必要でなければなくても良いです。）

流れで表すと下のようなイメージです。
```
GraphQL == クエリ結果 => Template == クエリ結果 => Componentの各パーツ
```

### 4. 作成したテンプレートを使用して、gatsby-node.jsでページをレンダリングする

さて、テンプレートページは完成したのであとは**Gatsby build**をするときにタグページを読み込ませるだけです。Gatsbyは最初に指定URLのページを読み込ませてビルドすることで静的なページが作成されていきます。

ここの**gatsby-node.js**で**src/templates/tags.js**に対してGraphQLで取得した結果をfor文でタグ数分生成しています。

```js
const path = require("path")
const _ = require("lodash")

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

  const blogPostTemplate = path.resolve("src/templates/blog.js")
  const tagTemplate = path.resolve("src/templates/tags.js")

  const result = await graphql(`
    {
      postsRemark: allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 2000
      ) {
        edges {
          node {
            fields {
              slug
            }
            frontmatter {
              tags
            }
          }
        }
      }
      tagsGroup: allMarkdownRemark(limit: 2000) {
        group(field: frontmatter___tags) {
          fieldValue
        }
      }
    }
  `)

  // handle errors
  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

  const posts = result.data.postsRemark.edges

  // Create post detail pages
  posts.forEach(({ node }) => {
    createPage({
      path: node.fields.slug,
      component: blogPostTemplate,
    })
  })

  // Extract tag data from query
  const tags = result.data.tagsGroup.group

  // Make tag pages
  tags.forEach(tag => {
    createPage({
      path: `/tags/${_.kebabCase(tag.fieldValue)}/`,
      component: tagTemplate,
      context: {
        tag: tag.fieldValue,
      },
    })
  })
}
```

### 5. すべてのタグのリストを表示するタグインデックスページを作成する（**/tags**）

今度はタグ一覧ページを作成していきます。前に書いたGraphQLクエリーでタグ一覧を取得し、Templateに取得結果を当てはめていきます。

```js
import React from "react"
import PropTypes from "prop-types"

// Utilities
import kebabCase from "lodash/kebabCase"

// Components
import { Helmet } from "react-helmet"
import { Link, graphql } from "gatsby"

const TagsPage = ({
  data: {
    allMarkdownRemark: { group },
    site: {
      siteMetadata: { title },
    },
  },
}) => (
  <div>
    <Helmet title={title} />
    <div>
      <h1>Tags</h1>
      <ul>
        {group.map(tag => (
          <li key={tag.fieldValue}>
            <Link to={`/tags/${kebabCase(tag.fieldValue)}/`}>
              {tag.fieldValue} ({tag.totalCount})
            </Link>
          </li>
        ))}
      </ul>
    </div>
  </div>
)

TagsPage.propTypes = {
  data: PropTypes.shape({
    allMarkdownRemark: PropTypes.shape({
      group: PropTypes.arrayOf(
        PropTypes.shape({
          fieldValue: PropTypes.string.isRequired,
          totalCount: PropTypes.number.isRequired,
        }).isRequired
      ),
    }),
    site: PropTypes.shape({
      siteMetadata: PropTypes.shape({
        title: PropTypes.string.isRequired,
      }),
    }),
  }),
}

export default TagsPage

export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(limit: 2000) {
      group(field: frontmatter___tags) {
        fieldValue
        totalCount
      }
    }
  }
`
```

## カテゴリーの実装

カテゴリー機能も考え方はタグと一緒です。タグは１つの記事に対し複数個の配列で構成され、カテゴリーは1記事に対して、単一の項目なので文字列としてマークダウンファイルに記述する必要があります。

### 実装手順
1. **markdown**ファイルに文字列型のカテゴリーを追加する
2. GraphQLクエリを作成し、全てのカテゴリーを取得する
3. カテゴリーページテンプレートを作成する
4. 作成したテンプレートを使用して、gatsby-node.jsでページをレンダリングする
5. すべてのカテゴリーのリストを表示するインデックスページを作成する

> 実装内容はほぼ一緒なので割愛します

# まとめ

いかがだったでしょうか？ちょっと今回は難しかったでしょうか？このブログのソースコードは公開されているので良ければ参考にどうぞ。それでは次回の記事で。

> https://github.com/yoshiki-0428/engneer-blog/blob/master/src/templates/tags-list-template.js#L29-L53