Gatsbyブログ その6 カテゴライズ

July 22, 2021

はじめに

公式サイトにあるCreating Tags Pages for Blog Postsを参考にしながら、このブログにある記事をカテゴライズしていく。このブログはgatsby-starter-blogベースなので、公式のコードをいくつか修正する必要があった。

カテゴライズでやりたいことは3つある。

  • カテゴリ毎の記事一覧ページを作成する(=タグページ/tags/tag)
  • カテゴリページを作成する(=タグインデックスページ/tags)
  • 記事にタグを載せる、タグとタグページをリングさせる

以下の手順でやってく。

  1. 記事にタグを追加
  2. タグ収集クエリを作成
  3. タグページのテンプレートを作成
  4. テンプレートからタグページを生成
  5. タグインデックスページを生成
  6. 記事一覧ページにタグを表示

1. 記事にタグを追加

各記事にtags:["xxx"]を追加、“xxx”がカテゴリーとなる。 いくつかの記事にgatsby,misc,imguiをとりあえず追加。

---
title: Gatsbyブログ その6 カテゴライズ1
date: "2021-07-22T19:46:00"
tags: ["gatsby"] //ここ追加
description: Gatsbyブログ その6 タグを使ってカテゴリページをつくる
---

2. タグ収集クエリを作成

GraphQLで全記事のタグ情報を収集するクエリを作成する。

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

以下実行結果。 tagはカテゴリ名、totalCountはカテゴリ毎の記事数となる。

{
  "data": {
    "allMarkdownRemark": {
      "group": [
        {
          "tag": "gatsby",
          "totalCount": 6
        },
        {
          "tag": "imgui",
          "totalCount": 1
        },
        {
          "tag": "misc",
          "totalCount": 1
        }
      ]
    }
  },
  "extensions": {}
}

3. タグページテンプレートを作成

タグページテンプレートはtags/[tag]のページを作成するためのテンプレート。 公式にあるコードをsrc/templates/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
          }
        }
      }
    }
  }
`

4. テンプレートからタグページを生成

gatsby-node.jsを修正し、テンプレートから具体的なタグページを生成する。 公式のコードを参考に一部修正した。 graphqlでタグ情報を取得、タグ数分createPageを生成しているところが大事。

const path = require(`path`)
const _ = require("lodash")
const { createFilePath } = require(`gatsby-source-filesystem`)

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

  // Define a template for blog post
  const blogPost = path.resolve(`./src/templates/blog-post.js`)

  // Get all markdown blog posts sorted by date
  const result = await graphql(
    `
      {
        postsRemark: allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          nodes {
            id
            fields {
              slug
            }
            frontmatter {
              tags
            }
          }
        }
        tagsGroup: allMarkdownRemark(limit: 2000) {
          group(field: frontmatter___tags) {
            fieldValue
          }
        }
      }
    `
  )

  if (result.errors) {
    reporter.panicOnBuild(
      `There was an error loading your blog posts`,
      result.errors
    )
    return
  }

  const posts = result.data.postsRemark.nodes

  // Create blog posts pages
  // But only if there's at least one markdown file found at "content/blog" (defined in gatsby-config.js)
  // `context` is available in the template as a prop and as a variable in GraphQL

  if (posts.length > 0) {
    posts.forEach((post, index) => {
      const previousPostId = index === 0 ? null : posts[index - 1].id
      const nextPostId = index === posts.length - 1 ? null : posts[index + 1].id

      createPage({
        path: post.fields.slug,
        component: blogPost,
        context: {
          id: post.id,
          previousPostId,
          nextPostId,
        },
      })
    })
  }

  // Extract tag data from query
  const tagTemplate = path.resolve("./src/templates/tags.js")
  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を作成する。 公式にあるコードをsrc/pages/tags.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
      }
    }
  }
`

この時点で/tags/gatsbyにアクセスするとgatsbyタグのある記事一覧を確認できる。

tag gatsby page

/tags/にアクセスするとタグインデックスページを確認できる。

tags page

6. 記事一覧ページにタグを表示

記事にタグを載せて、タグページとリンクさせる。 記事一覧ページのpages/index.jsを修正していく。 まず、graphqlのfrontmattertagsを追加し、タグ情報を扱えるようにする。

allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
  nodes {
    excerpt
    fields {
      slug
    }
    frontmatter {
      date(formatString: "MMMM DD, YYYY")
      title
      tags //ここ追加
      description
    }
  }
}

タグを表示したい箇所にTagsコンポーネントをおく。 import Tags from "../components/tags"をインポートしておくこと。 コンポーネントの詳細はこの後でやる。

<header>
  <h2>
    <Link to={post.fields.slug} itemProp="url">
      <span itemProp="headline">
        <FontAwesomeIcon icon={faPenSquare} />
        {title}
      </span>
    </Link>
  </h2>
  <small>{post.frontmatter.date}</small>
  <Tags>{tags}</Tags> //ここ追加
</header>

コンポーネント/tags/components/tags.jsを作成 こちらのサイトhttps://dev.to/gabcimato/how-to-add-tags-to-your-gatsby-blog-part-1-34fk を参考にさせていただいた。 以下詳細。

import * as React from "react"
import { Link } from "gatsby"

const Tags = ({ children }) => {
  return children && (
    <ul style={{ marginBottom: 0, marginLeft: 10, display: "inline-flex" }}>
      {children.map(tag => (
        <Link to={`/tags/${tag}/`}>
          <li
            key={tag}
            style={{
              borderRadius: `4px`,
              padding: `1px 4px`,
              marginRight: `5px`,
              fontSize: `80%`,
              backgroundColor: "#005b99",
              color: "white",
              listStyle: "none",
            }}
          >
            #{tag}
          </li>
        </Link>
      ))}
    </ul>
  )
}
  
export default Tags

これでタグの表示とタグページへのリンクができた。

index tag

個々の記事についても同様に、Tagsコンポーネントを埋め込むだけで表示できる。


Profile picture

Written by yanofu

© 2021, yanofu