microCMS+Gatsby製ブログでタグ一覧に件数をつける
さて、最近ブログを少しずつ改良しています。
このブログにはタグおよびカテゴリを記事ごとに付与しています。タグは1記事に複数、カテゴリは1記事ひとつまでのルールです。(あまり整理はうまくいってませんが..)
さて、このタグおよびカテゴリは一覧表示をブログ中で行っているのですが、よく見る
- タグの中に記事数を表示させる
- 記事の多い順にタグを並べる
ということをしたいと考えました。
このブログはmicroCMSとGatsbyで構築しており、タグやカテゴリもmicroCMSのただのコンテンツの一種です。
microCMSのAPIは、参照しているコンテンツはできますが、被参照されているコンテンツを取得することができません。(もしできたら教えてください)
うーん、microCMSのAPI、被参照数を返してくれないかなあ... ブログのタグ一覧に記事数表示させたいけどかなり強引な実装をするしかなさそうだ #microcms
— Yusuke Kanda (@uskan_d) November 3, 2022
というわけで強引に実装していきましょう。
前提
ブログにカテゴリ、タグを設定しており、gatsbyでgatsby-source-microcmsを利用していること。
gatsby-config.jsでcategory, tagの設定を以下のように行っており、GraphQLにMicrocmsCategory, MicroCMSTagがあること。
{
resolve: "gatsby-source-microcms",
options: {
apiKey: process.env.API_KEY,
serviceId: 'your_subdomain',
apis: [
{
endpoint: "blog",
},
{
endpoint: "category",
},
{
endpoint: "tag",
},
],
},実装
せっかくGatsbyとmicroCMSを使っているので、できるだけGraphQLに乗っかりたいところです。
gatsbyではGatsby Node APIの中で、GraphQLスキーマの作成中に呼び出されるsetFieldsOnGraphQLNodeTypeの設定が可能です。
これにより、GraphQLの値を追加することができます。
gatsby-node.js内に以下を記述
exports.setFieldsOnGraphQLNodeType = require('./src/setFieldsOnGraphQLNodeType');
以下
const { GraphQLInt } = require("gatsby/graphql");
const axios = require('axios');
module.exports = async args => {
//カテゴリ一覧に件数を追加
const apiKey = process.env.API_KEY;
if(args.type.name === "MicrocmsCategory"){
return {
blogsCount: {
type: GraphQLInt,
args: {},
resolve: async (source, fieldArgs) => {
const res = await axios.get('https://your-subdomain.microcms.io/api/v1/blog?filters=category[equals]'+source.categoryId, {headers: {'X-MICROCMS-API-KEY': apiKey}});
return res.data.totalCount;
}
}
}
}
//カテゴリ一覧に件数を追加
if(args.type.name === "MicrocmsTag"){
return {
blogsCount: {
type: GraphQLInt,
args: {},
resolve: async (source, fieldArgs) => {
const res = await axios.get('https://your-subdomain.microcms.io/api/v1/blog?filters=tags[contains]'+source.tagId, {headers: {'X-MICROCMS-API-KEY': apiKey}});
return res.data.totalCount;
}
}
}
}
return {}
};GraphQLの処理に介入する以上GraphQLは使えない(はず...)ので、axiosでmicroCMS APIを叩く形に。
カテゴリ一覧を表示するコンポーネント内でblogsCountが参照できるようになりました。
下記のようにソート及び表示を行います。
export const Categories = ({ categories }) => {
return (
<Box sx={{paddingTop: "1em", marginBottom: "1em"}}>
<Card sx={{padding: ".5em"}}>
<Typography sx={{fontSize: 18, fontWeight: "bold"}} color="text.secondary" gutterBottom>
カテゴリ一覧
</Typography>
{categories.sort((a,b)=>b.blogsCount - a.blogsCount).map(category => (
<Chip key={category.categoryId} label={category.blogsCount ? category.name + "(" + category.blogsCount + ")" : category.name} icon={<Article />} sx={{lineHeight: 2, margin: ".25em .25em .25em 0", padding: ".3em .5em .3em .5em ", "&:hover": { backgroundColor: "#ccc"}}} onClick={event => {
event.preventDefault()
navigate("/blog/category/" + category.categoryId)
}}>
</Chip>
))}
</Card>
</Box>
)
}
これで、
結果
適用前
適用後
綺麗にカテゴリとタグの中身が並びました。
問題点
この実装は要するにN+1問題を抱えているので、カテゴリ一覧をGraqhQLで取得しようとするとカテゴリ数分のAPIリクエストが発生します。
そのため、gatsby buildが遅くなります。
根本的には、microCMSのAPIが被参照を返してくれる仕組みを作ってくれないと解決は難しいと思われます。
今回の場合はどの画面からでも結果生成されるコンポーネントの内容はいっしょなので、Gatsby/Reactでうまくキャッシュできないか検討してみたいと思います。
