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

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

Category
Description
今回は「エンジニア初心者でもできる」を前提に以下の構成で記事を作成していこうと思います。
Author
Published
Published May 26, 2020
Last Updated
Last Updated May 26, 2020
Writings
この記事は約17分で読めます

はじめに

今回は「エンジニア初心者でもできる」を前提に以下の構成で記事を作成していこうと思います。

内容

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

実装する前に

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

手順

  1. markdownファイルにタグを追加する
  1. GraphQLクエリを作成し、全てのタグを取得する
  1. タグページテンプレートを作成する(/tag/{tag}
  1. 作成したテンプレートを使用して、gatsby-node.jsでページをレンダリングする
  1. すべてのタグのリストを表示するタグインデックスページを作成する(/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**にアクセスします。
下のように画面が見えるはずです。
notion image
画面が表示されたら下のクエリーを入力してみましょう。
{ allMarkdownRemark { group(field: frontmatter___tags) { tag: fieldValue totalCount } } }
タグ一覧が取得できるはずです。
notion image
ちなみに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ファイルに文字列型のカテゴリーを追加する
  1. GraphQLクエリを作成し、全てのカテゴリーを取得する
  1. カテゴリーページテンプレートを作成する
  1. 作成したテンプレートを使用して、gatsby-node.jsでページをレンダリングする
  1. すべてのカテゴリーのリストを表示するインデックスページを作成する
実装内容はほぼ一緒なので割愛します

まとめ

いかがだったでしょうか?ちょっと今回は難しかったでしょうか?このブログのソースコードは公開されているので良ければ参考にどうぞ。それでは次回の記事で。
https://github.com/yoshiki-0428/engneer-blog/blob/master/src/templates/tags-list-template.js#L29-L53