Starterにタグによる記事分類がなかったので自分で実装します。コードは一部省略してますが、Contentful Gatsby Starter Blogをいじってる感じです。
ContentfulでTagsというContent modelを作ります。Contentfulのガイドに沿って作成したStarterではBlog postのfieldとしてtags fieldが生成されているのですが、タグをブログ記事などと同じようなcontent modelで定義しておくと後でいろいろ便利っぽいです。
タグごとのページなども作成したいのでtitleとslugを設定。titleとslugに分けたのは日本語のタグにも英字URLを当てたい(心理学: psychologyなど)のでそうしましたが、後述するようにtitle=slugにしたほうが楽は楽でした。一応Hero Imageも設定できるようにしておきました。Refference fieldでタグの親子関係もできそうですが、階層型で整理するのも大変そうなのでとりあえず保留です。
tags modelを作ったので次はBlog postのfieldから参照できるようにします。
Add fieldからReferenceを選択
複数のタグを参照するので、Many referencesにチェックを入れてCreateします。
設定のValidationでTagsモデルを参照するようにします。
これで各Blog postと各Tagを紐付けられるようになりました。
Gatsbyで各タグページを作成します。StarterでのBlog postを参考に似たロジックで作ります。
Tagsで作ったtitleとslugをGraphQLで引っ張ります。
allContentfulTags {
edges {
node {
title
slug
}
}
}
Blog postをcreatePageで各Tagページを作ります。contextでslugを渡しておきます。
// tagページ作成のcomponent
const tagTemplate = path.resolve('./src/templates/tag-template.js')
// 中略
// resolve(graphql(`...`).then(result => {
const tags = result.data.allContentfulTags.edges;
tags.forEach(tag => {
createPage({
path: `tags/${tag.node.slug}/`,
component: tagTemplate,
context: {
slug: tag.node.slug
},
})
})
これでtags/タグ名
で各タグページが作成されました。
/tags/
でタグ一覧が表示されるようにします。
一覧ページではタグごとの記事数も表示したいと思います。Gatsbyはグループ化してfetchすることでtotalCountで総数を取得できるので、これを利用してタグごとの記事数を表示できます。Tagsのslugでfetchした場合はfieldValueにslugが入ることになります。前述の通りslugがそのままタグ名を表していれば楽なのですが、tagのtitleとslugを分けてしまったので、titleは別途突き合わせて表示させてあげる必要があるみたいです。
query TagIndexQuery {
allContentfulBlogPost {
group(field: tags___slug) {
fieldValue
totalCount
}
}
allContentfulTags {
edges {
node {
slug
title
blog_post {
slug
}
}
}
}
}
2021/1/11追記: 不要なクエリを削除しました
js部分は以下みたいな感じです。mapオブジェクトを連想配列に変換するスッキリする書き方に悩んだのですが、困ったときのstack overflowです。
タグ一覧はとりあえずで記事一覧を並べるコンポーネントを流用して並べました。
import React from "react";
import { graphql, Link } from "gatsby";
import get from "lodash/get";
import Layout from "../components/layout";
import SEO from "../components/seo";
class BlogIndex extends React.Component {
render() {
// 各タグのtitleとslugの組み合わせ用
const tags = get(this, "props.data.allContentfulTags.edges");
// groupでfetchしたfieldValueとtotalCountのmapオブジェクトを取得
const tagsGroup = get(this, "props.data.allContentfulBlogPost.group");
// mapオブジェクトを連想配列に変換
const tagsCount = Object.assign(...tagsGroup.map(({fieldValue, totalCount}) => ({[fieldValue]: totalCount})));
return (
<Layout location={this.props.location}>
<SEO title="タグ一覧" desc="タグ一覧ページ" noindex />
<div style={{ background: "#fff", textAlign: "center" }}>
<div className="wrapper">
<h2 className="section-headline">Tags</h2>
<ul className="article-list">
{tags.map(({ node }) => {
return (
<li key={node.slug}>
<Link to={node.slug}>{node.title} ({tagsCount[node.slug]})</Link>
</li>
);
})}
</ul>
</div>
</div>
</Layout>
);
}
}
結果