はじめに
本記事は https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags のクロスポスト記事になります。
この記事はGatsbyというヘッドレスCMS技術で構成されています。今回は「エンジニア初心者でもできる」を前提に以下の構成で記事を作成していこうと思います。
- Gatsby始めるまで
- GatsbyにShare機能、OGPタグをつける
- タグ機能、カテゴリ機能をつける(基礎編)
- タグ機能、カテゴリ機能をつける(応用編)
- GatsbyにTableOfContents(目次)をつける
- DarkModeをつける
内容
今回はWordPressのタグ機能、カテゴリ機能に当たる部分を実装していきます。標準で実装されてないの?!って突っ込みはあるかと思いますが、そうです。標準のテンプレートでは実装されておりません。なので実装するよりも最初からあるテンプレートを選んだほうが良いです。
実装する前に
まずこちらの記事を読みましょう。GatsbyJS本家でタグの付け方について記載されています。
手順
- markdownファイルにタグを追加する
- GraphQLクエリを作成し、全てのタグを取得する
- タグページテンプレートを作成する(/tag/{tag})
- 作成したテンプレートを使用して、gatsby-node.jsでページをレンダリングする
- すべてのタグのリストを表示するタグインデックスページを作成する(/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**にアクセスします。
下のように画面が見えるはずです。
画面が表示されたら下のクエリーを入力してみましょう。
{
allMarkdownRemark {
group(field: frontmatter___tags) {
tag: fieldValue
totalCount
}
}
}
タグ一覧が取得できるはずです。
ちなみに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.jsでsrc/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記事に対して、単一の項目なので文字列としてマークダウンファイルに記述する必要があります。
実装手順
- markdownファイルに文字列型のカテゴリーを追加する
- GraphQLクエリを作成し、全てのカテゴリーを取得する
- カテゴリーページテンプレートを作成する
- 作成したテンプレートを使用して、gatsby-node.jsでページをレンダリングする
- すべてのカテゴリーのリストを表示するインデックスページを作成する
実装内容はほぼ一緒なので割愛します
まとめ
いかがだったでしょうか?ちょっと今回は難しかったでしょうか?このブログのソースコードは公開されているので良ければ参考にどうぞ。それでは次回の記事で。
https://github.com/yoshiki-0428/engneer-blog/blob/master/src/templates/tags-list-template.js#L29-L53