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>
+ タグ: 
+ {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
+ }
+ }
+ }
+ }
+ }
+