概要
開設当初からブログのタグ付をしたかったが、実装に時間がかかりそうだったので諦めていた。
今回少し時間ができたので実装してみた。
実装の流れ
- tag 一覧表示ページのを作成する。
- 各 tag の記事一覧ページを作成する。
- 記事の YAML Front Matter に記載した tag 情報を読めるようにする。
- 各記事に該当する tag を表示する。
tag 一覧表示ページのを作成する
あまり利用を想定していないので公式から変更なし。
新しい tag をつけるときの参考や重複防止が主な役割なのでスタイルすら当てていない。
各 tag の記事数が出るところが管理上地味に 便利。
各 tag の記事一覧ページを作成する
tag ごとの記事一覧ページは Home の記事一覧ページをコピーして流用した。
import React from "react"
import PropTypes from "prop-types"
import { Link, graphql } from "gatsby"
import { CssBaseline, Grid, Typography } from "@mui/material"
import Layout from "../components/layout"
import SEO from "../components/seo"
import Postcard from "../components/postscard"
const Tags = ({ pageContext, data, location }) => {
const siteTitle = data.site.siteMetadata?.title || `Title`
const { tag } = pageContext
const { edges, totalCount } = data.allMarkdownRemark
const tagHeader = `${tag}:(${totalCount})`
return (
<React.Fragment>
<CssBaseline />
<Layout location={location} title={siteTitle}>
<SEO title={`#` + tag} />
<header>
<Typography variant="h5" component="div" itemProp="headline">
{`#` + tagHeader}
</Typography>
</header>
<hr />
<ul>
{edges.map(({ node }) => {
return <Postcard post={node} />
})}
</ul>
{/*
This links to a page that does not yet exist.
You'll come back to it!
*/}
<Link to="/tags">All tags</Link>
</Layout>
</React.Fragment>
)
}
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
}
excerpt(pruneLength: 160)
html
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
description
}
}
}
}
site {
siteMetadata {
title
}
}
}
`
記事の YAML Front Matter に記載した tag 情報を読めるようにする
gatsby-node.js を編集する。
tag をそのまま URL にして利用するためスペースが入ると何かと厄介なので tag の文字列をケバブケースに変換する。
ケバブケースはスペースをハイフンに変換した文字形態のこと。例)-□□-□-
語源は縦にするとケバブっぽいからみたい……なるほど!
const _ = require("lodash")
~~~略~~~
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
// Define a template for blog post
const blogPost = path.resolve(`./src/templates/blog-post.js`)
const TagTemplate = path.resolve(`./src/templates/tags.js`)
// Get all markdown blog posts sorted by date
const result = await graphql(
`
{
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: ASC }
limit: 1000
) {
nodes {
id
frontmatter {
title
description
published
tags
}
fields {
slug
}
}
}
tagsGroup: allMarkdownRemark(limit: 2000){
group(field: frontmatter___tags){
fieldValue
}
}
}
`
)
~~~略~~~
const tags = result.data.tagsGroup.group
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag.fieldValue)}/`,
component: TagTemplate,
context: {
tag: tag.fieldValue,
},
})
})
}
}
各記事に該当する tag を表示する
blog-post.js を編集する。
日付の下に Mui の Crip を使って実装した。
tag 名はハッシュタグっぽく先頭に#をつけた。
クリップをクリックするとそれぞれの tag の記事一覧に飛ぶ。
import _ from "lodash"
;<header>
<Typography variant="h5" component="div" itemProp="headline">
{post.frontmatter.title}
</Typography>
<Typography variant="subtitle1" component="div" color="textSecondary">
{" "}
{post.frontmatter.date}{" "}
</Typography>
<Stack
direction="row"
justifyContent="flex-start"
alignItems="center"
spacing={1}
>
{post.frontmatter.tags.map(tag => {
return (
<Chip
label={`#` + tag}
component="a"
href={`/tags/${_.kebabCase(tag)}/`}
style={{ textDecoration: "none" }}
clickable
/>
)
})}
</Stack>
</header>
tag が設定されていない記事で下記のようなエラーが出た。
failed Building static HTML for pages - 2.685s
error Building static HTML failed for path "/posts/p26/"
43 | spacing={1}
44 | >
> 45 | {post.frontmatter.tags.map(tag => {
| ^
46 | return (
47 | <Link to={`/tags/${_.kebabCase(tag)}/`}>
48 | <Chip label={tag} />
WebpackError:TypeError: Cannot read property 'map' of null (from plugin: gats by-plugin-material-ui)
tag が設定されていないことを考慮して編集した。
{
post.frontmatter.tags?.map(tag => {
return (
<Chip
label={`#` + tag}
component="a"
href={`/tags/${_.kebabCase(tag)}/`}
style={{ textDecoration: "none" }}
clickable
/>
)
})
}
まとめ
開設当初からやりたい機能が実装ができて満足した。
tag が乱立するとあまり意味を成さないので運用面では課題が残る。
現在の更新ペースであればさほど問題にならないが、ループの回数が増えたためかビルド時間が長くなった。
参考
おしまい