Gatsby ブログにタグ導入

August 17, 2020

はじめに

タグ機能の追加方法は公式ドキュメントに全て記載されています。

以上。

・・・なんですが、備忘録含めて gatsby-starter-blog スターターに対して適用した場合の実差分を記載しておきます。

実差分

タグ情報を GraphQL で取得し、タグページを生成するコードが追加されています。

--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -1,10 +1,12 @@
 const path = require(`path`)
+const _ = require(`lodash`)
 const { createFilePath } = require(`gatsby-source-filesystem`)
 
 exports.createPages = async ({ graphql, actions }) => {
   const { createPage } = actions
 
   const blogPost = path.resolve(`./src/templates/blog-post.js`)
+  const tagTemplate = path.resolve(`./src/templates/tags.js`)
   const result = await graphql(
     `
       {
@@ -19,10 +21,16 @@ exports.createPages = async ({ graphql, actions }) => {
               }
               frontmatter {
                 title
+                tags
               }
             }
           }
         }
+        tagsGroup: allMarkdownRemark(limit: 2000) {
+          group(field: frontmatter___tags) {
+            fieldValue
+          }
+        }
       }
     `
   )
@@ -48,6 +56,19 @@ exports.createPages = async ({ graphql, actions }) => {
       },
     })
   })
+
+  // 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,
+      },
+    })
+  })
 }
 
 exports.onCreateNode = ({ node, actions, getNode }) => {

タグ一覧ページを追加しています。既存 gatsby-starter-blog ページに合わせています。

--- /dev/null
+++ b/src/pages/tags.js
@@ -0,0 +1,72 @@
+import React from "react"
+import PropTypes from "prop-types"
+
+// Utilities
+import kebabCase from "lodash/kebabCase"
+
+// Components
+import { Link, graphql } from "gatsby"
+import Layout from "../components/layout"
+import SEO from "../components/seo"
+
+const TagsPage = ({
+  location,
+  data: {
+    allMarkdownRemark: { group },
+    site: {
+      siteMetadata: { title },
+    },
+  },
+}) => (
+  <Layout location={location} title={title}>
+  <SEO title="All tags" />
+    <div>
+      <h1>タグ一覧</h1>
+      <ul>
+        {group.map(tag => (
+          <li key={tag.fieldValue}>
+            <Link to={`/tags/${kebabCase(tag.fieldValue)}/`}>
+              {tag.fieldValue} ({tag.totalCount})
+            </Link>
+          </li>
+        ))}
+      </ul>
+    </div>
+  </Layout>
+)
+
+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
+      }
+    }
+  }
+`

タグ情報を元に各記事毎に設定されているタグページへのリンクを追加します。

--- a/src/templates/blog-post.js
+++ b/src/templates/blog-post.js
@@ -4,6 +4,7 @@ import { Link, graphql } from "gatsby"
 import Layout from "../components/layout"
 import SEO from "../components/seo"
 import { rhythm, scale } from "../utils/typography"
+import kebabCase from "lodash/kebabCase"
 
 const BlogPostTemplate = ({ data, pageContext, location }) => {
   const post = data.markdownRemark
@@ -35,6 +36,12 @@ const BlogPostTemplate = ({ data, pageContext, location }) => {
           >
             {post.frontmatter.date}
           </p>
+          <p>
+            タグ:&ensp;
+            {post.frontmatter.tags
+              .map((tag) => (<Link to={`/tags/${kebabCase(tag)}/`}>{tag}</Link>))
+              .reduce((prev, curr) => [prev, ', ', curr])}
+          </p>
         </header>
         <section dangerouslySetInnerHTML={{ __html: post.html }} />
         <hr
@@ -92,6 +99,7 @@ export const pageQuery = graphql`
       frontmatter {
         title
         date(formatString: "MMMM DD, YYYY")
+        tags
       }
     }
   }

各タグページ生成用テンプレートを追加します。 既存 gatsby-starter-blog ページに合わせています。

--- /dev/null
+++ b/src/templates/tags.js
@@ -0,0 +1,106 @@
+import React from "react"
+import PropTypes from "prop-types"
+
+// Components
+import { Link, graphql } from "gatsby"
+import Layout from "../components/layout"
+import SEO from "../components/seo"
+
+import { rhythm } from "../utils/typography"
+
+const Tags = ({ pageContext, data, location }) => {
+  const { tag } = pageContext
+  const siteTitle = data.site.siteMetadata.title
+  const { edges, totalCount } = data.allMarkdownRemark
+  const tagHeader = `"${tag}"タグが付いた記事(${totalCount}件)`
+
+  return (
+    <Layout location={location} title={siteTitle}>
+      <SEO title="tags" />
+
+      <h1>{tagHeader}</h1>
+
+      {edges.map(({ node }) => {
+        const title = node.frontmatter.title || node.fields.slug
+        return (
+          <article key={node.fields.slug}>
+            <header>
+              <h3
+                style={{
+                  marginBottom: rhythm(1 / 4),
+                }}
+              >
+                <Link style={{ boxShadow: `none` }} to={node.fields.slug}>
+                  {title}
+                </Link>
+              </h3>
+              <small>{node.frontmatter.date}</small>
+            </header>
+            <section>
+              <p
+                dangerouslySetInnerHTML={{
+                  __html: node.excerpt,
+                }}
+              />
+            </section>
+          </article>
+        )
+      })}
+      <Link to="/tags">タグ一覧</Link>
+    </Layout>
+  )
+}
+
+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) {
+    site {
+      siteMetadata {
+        title
+      }
+    }
+    allMarkdownRemark(
+      limit: 2000
+      sort: { fields: [frontmatter___date], order: DESC }
+      filter: { frontmatter: { tags: { in: [$tag] } } }
+    ) {
+      totalCount
+      edges {
+        node {
+          excerpt
+          fields {
+            slug
+          }
+          frontmatter {
+            date(formatString: "MMMM DD, YYYY")
+            title
+          }
+        }
+      }
+    }
+  }
+

Product

「いつやるかは決めてないけれど、必ずやらなければいけないタスク」を忘れないためのアプリ