Gatsby.js + Markdownブログで、カテゴリー機能を実装する方法
ヘッドレスCMSを使っていればカテゴリーやタグの管理は楽ですが、Markdownの場合は一工夫必要です。
英数字のみのカテゴリーであればそこまで難しくありませんが、日本語の場合は「カテゴリー名は日本語、スラッグは英数字」というパターンも多いと思います。
また、SEO対策で、メタタグにそれぞれ工夫したディスクリプションをカテゴリー毎に用意したい場合もあるでしょう。
そういった場合に対応した、カテゴリーの管理方法とカテゴリーページの作り方です。もちろんタグページにも使えます。
動作環境:
- Node.js v18.12.1
- React v18.2.0
- Gatsby.js v5.6.0
- gatsby-transformer-json v5.6.0
カテゴリー用jsonファイルを作る
今回、カテゴリーはjsonファイルで管理します。ファイルはsrcフォルダー下にdataフォルダーを作り、その中に入れています。
※jsファイルでも可。その場合はgatsby-transformer-jsonは不要。
;[
{
title: "コメディー",
slug: "comedy",
description:
"コメディー映画の記事一覧です。コメディーと言えば私の中では三谷幸喜「ラヂオの時間」、当時映画館で爆笑しながら見ました。",
},
{
title: "ホラー",
slug: "horror",
description: "ホラー映画の記事一覧です。現実の私はホラー映画は見ませんが。",
},
{
title: "加山雄三",
slug: "kayamayuzo",
description:
"加山雄三さん出演の映画一覧です。永遠の若大将、これからも元気でいてほしいです!",
},
]こんな感じで、カテゴリーを作っておきます。もちろん、他のデータを追加してもかまいません。
タグの場合も同様にしてデータを用意して使い回せます。
Markdown記事内での管理
Markdown記事内では、メタデータはYAML Frontmatterを使って管理。その中で、記事が属するカテゴリーとして、カテゴリーのslugを付与します。
---
title: 腹筋崩壊!涙を流して笑える、コメディー映画10選
slug: funniest-10-movies
category:
- comedy
date: 2022-10-11
---
本文あれやこれや上記の例ではカテゴリー「comedy」を指定しています。カテゴリーは複数でも問題ありません。
gatsby-transformer-jsonをインストール
次に、GraphQLでカテゴリーデータを取得するため、公式プラグインのgatsby-transformer-jsonをインストールします。
# npmの場合
npm install gatsby-transformer-json
# yarnの場合
yarn add gatsby-transformer-jsonさらに、gatsby-config.jsにプラグインの追加と、gatsby-source-filesystemでjsonファイルのディレクトリを追加しておきます。
module.exports = {
//...
plugins: [
//...
`gatsby-transformer-json`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `data`,
path: `${__dirname}/src/data/`,
},
},
],
}これで、カテゴリーデータのクエリCategoryJsonを、GraphQLで取得できるようになります。
Frontmatterのカテゴリーslugと、CategoryJsonのデータを紐付ける
この時点ではまだ、Frontmatterで記事に追加したカテゴリーと、CategoryJsonのデータは紐付いていません。紐付けに使えるのは、CategoryJsonのslugですね。
Frontmatterのカテゴリーに指定された文字列と一致するCategoryJsonのslugを紐付けて、Frontmatterのカテゴリーからカテゴリーのタイトルなどを取得できるようにします。
gatsby-node.jsにコードを追加。
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type MarkdownRemark implements Node {
frontmatter: Frontmatter
}
type Frontmatter implements Node {
category: [CategoryJson] @link(by: "slug")
}
`
createTypes(typeDefs)
}createSchemaCustomizationは、元来別々のNodeどうしに関連付けを行うためのAPIです。
参考 Create foreign key relationships between data - Creating a Source Plugin | Gatsby
これで、Frontmatterのカテゴリーslugと、category.jsonのslugが結びつき、GraphQLで記事スキーマの中にカテゴリー情報が追加されます。
query MyQuery {
markdownRemark {
frontmatter {
category
}
}
}query MyQuery {
markdownRemark {
frontmatter {
category {
id
title
slug
}
}
}
}カテゴリーページのパスを生成
ここからは、カテゴリーページのパス(URL)を作っていきます。gatsby-node.jsの出番です。
ポイントは、クエリ取得にcategoryJsonは使わない点。
というのは、category.jsonの中に用意されたカテゴリーがすべて、必ずしも記事で使われているかはわからないためです。使っていないカテゴリーがある場合、そのカテゴリーページのパスが生成されても困りますよね。
そのため、ここではallMarkdownRemarkから、カテゴリーのグループを利用します。GatsbyのGraphQLは、これがあるから便利なんですよね~😺
参考 Group - GraphQL Query Options | Gatsby
記事のFrontmatterに存在するカテゴリーのslugだけが抽出されるので、「categoryJsonにはあるけれど、実際は記事に使われていないカテゴリー」のパスは生成されません。
これを踏まえて、まずはクエリ追加。
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const blogresult = await graphql(`
query {
allMarkdownRemark{
group(field: { frontmatter: { category: SELECT } }) {
fieldValue
totalCount
}
...その他色々
}
}
`)
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
// 以下に続く
}この後に、createPageでページパスの生成をします。
パス生成時には、コンテキストとしてカテゴリーのスラッグ(ここでは$cat_slug)を用意、テンプレート側で受け取れるようにします。
// 上記コードの続き
const catPostPerPage = 10 // 1ページあたりの記事数
blogresult.data.allMarkdownRemark.group.forEach(node => {
const catPosts = node.totalCount
const catPages = Math.ceil(catPosts / catPostPerPage)
Array.from({ length: catPages }).forEach((_, i) => {
createPage({
path:
i === 0
? `/category/${node.fieldValue}` // 最初のページ
: `/category/${node.fieldValue}/page/${i + 1}`, // 2ページ目以降
component: path.resolve(`./src/templates/cat-template.js`), // テンプレート指定
context: {
cat_slug: node.fieldValue, // カテゴリースラッグをテンプレートに送る
skip: catPostPerPage * i,
limit: catPostPerPage,
},
})
})
})ここでいったん、ローカルでGatsby.jsを立ち上げて、ブラウザ上で404ページ(存在しないページ)を表示してみましょう。生成されたパスが確認できるはずです。
カテゴリーテンプレートを編集
カテゴリーページのテンプレートでは、GraphQLから2つの階層を利用します。
- カテゴリー情報である
categoryJson→ カテゴリーのタイトルやディスクリプション - 全記事
allMarkdownRemark→ そのカテゴリーに属する記事すべて
gatsby-node.jsでのカテゴリーページ生成時に、コンテキストとしてcat_slugを作りました。そのcat_slugを使い、表示するカテゴリーや記事一覧の絞り込みをします。
データ取得の例:
export const query = graphql`
query ($cat_slug: String!, $skip: Int!, $limit: Int!) {
allMarkdownRemark(
filter: {
frontmatter: { category: { elemMatch: { slug: { eq: $cat_slug } } } }
}
) {
nodes {
id
html
frontmatter {
title
slug
}
}
}
categoryJson(slug: { eq: $cat_slug }) {
title
slug
description
}
}
`あとはこれを使って、カテゴリーテンプレート内でタイトルを出力するなりすればOK👌!