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

Programming

はじめに

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

内容

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

テンプレートのおすすめはこちら

実装する前に

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ファイルにタグを追加する

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

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

I went to the zoo today. It was terrible.

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

---
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にアクセスします。

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

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

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

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

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

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

ディレクトリはsrc/template/tags.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.jssrc/templates/tags.jsに対してGraphQLで取得した結果をfor文でタグ数分生成しています。

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に取得結果を当てはめていきます。

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記事に対して、単一の項目なので文字列としてマークダウンファイルに記述する必要があります。

実装手順

  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

#CMS#Gatsby

Related Links


Gatsbyでブログを始めるまで
GatsbyにShare機能、OGPタグをつける
タグ機能、カテゴリ機能をつける(応用編)
GatsbyにTableOfContents(目次)をつける
DarkModeのCSSを適用させる
GatsbyブログのSEO対策でやっておくこと一覧
多機能なGatsbyJSのThemeをつくってnpmに公開した話
Yoshiki Ohashi
2x歳の個人事業主エンジニア。SI企業1年, Webベンチャー企業2年で上流から下流の経験を経て独立。 エンジニアらしく性格は温和。プロジェクトチームに心理的安全性を求める。go gin | Spring | Java | Kotlin | Vue | Python | 筋トレ | キャンプ | 個人開発 | 新潟出身

よく読まれている記事


Gatsbyでブログを始めるまで
GatsbyにShare機能、OGPタグをつける
Gatsbyにタグ機能、カテゴリ機能をつける(基礎編)
DL実装するときに理解すること
多機能なGatsbyJSのThemeをつくってnpmに公開した話
© 2020 Yoshiki Ohashi All rights reserved.