Gatsbyで作っているブログサイトで、Tagの数が多くなってきたのでTagをまとめたカテゴリ単位の表示をさせたい
JSONファイルに適当なマッピングを持たせてそれを参照させたいというようなケースが発生したのでやってみた
普通にJSONを読み込んでも良いが、せっかくGatsbyで作っているのでJSONからGatsbyのNodeとして扱いGraphQLからクエリできるようにしてみる
今回はgatsby-transformer-jsonを使う
install
yarn add gatsby-transformer-json
設定
const plugins: GatsbyConfig["plugins"] = [ + `gatsby-transformer-json`, + { + resolve: `gatsby-source-filesystem`, + options: { + path: `${__dirname}/content/definitions`, + name: `definitions`, + },
source-filesystemで特定ディレクトリ以下のファイルを読めるようにした
今回は定義ファイルなのでdefinitionsという名前にした
マッピングファイル
- content/definitions/categories.json(一部抜粋)
[ { "name": "Cloud", "tags": [ "AWS", "GoogleCloud", "Cloudflare" ] }, { "name": "エディタ", "tags": ["IntelliJ", "VS Code", "Vim"] } ]
とりあえずこの形式にした
ドキュメントや他記事を見る感じ、SchemaをカスタマイズしてGraphQLでタグの文字列とカテゴリを一発取得みたいなことも可能そうに見えたが
ぱっとできそうなイメージが湧かなかったのでまずはマッピングだけを用意し、データ取得するページ側で愚直に回してひもづけるようにした
型定義の生成
これでdevサーバを起動すれば関連する型定義は自動で生成してくれる
あらためて感じたがJSONの中身をみてこんな感じでしょっていうのをしっかり出してきてくれるので良い
Node名はCategoriesJsonになる
これはcategories.jsonというファイル名とtransformer-jsonがJSONを扱うtransformerなのでっぽい(ドキュメントまで調べてない
別のファイルで適当な名前をつけて中身を書いて(hoge.jsonとか)devサーバを起動するとHogeJsonというNodeができてファイルの中身に合わせた型定義が生成される
Tag一覧ページでカテゴリごとに分類する
かなり個別ケースになってしまうが下記のようなコンポーネントを実装した(切り貼りしているのでもしかしたら動かないかも)
import { graphql, PageProps, Link } from "gatsby"
import kebabCase from "lodash/kebabCase"
import React from "react"
type SummarizedTag = {
[key: string]: {
count: number
tags: { fieldValue: string | null; totalCount: number }[]
}
}
type Tag = {
fieldValue: string | null
totalCount: number
}
type TagsWithCountProps = {
tags: Tag[]
}
const TagsWithCount = ({ tags }: TagsWithCountProps) => {
return (
<span className="flex flex-row flex-wrap gap-1">
{tags.map(tag => (
<Link key={tag.fieldValue} aria-label="tag" className="label" to={`/tags/${kebabCase(tag?.fieldValue || "")}/`}>
{tag.fieldValue} ({tag.totalCount})
</Link>
))}
</span>
)
}
const TagsPage: React.FC<PageProps<Queries.TagsQuery>> = ({ data }) => {
const group = data.allMarkdownRemark.group
const categories = group.reduce((acc, tag) => {
const category =
data.allCategoriesJson.edges.find(({ node }) => node?.tags?.includes(tag.fieldValue))?.node.name || "Other"
const row = acc[category] || { count: 0, tags: [] }
const newData = { count: row.count + tag.totalCount, tags: [...row.tags, tag] }
return { ...acc, [category]: { ...newData } }
}, {} as SummarizedTag)
return (
<main className="h-full divide-y divide-zinc-100 bg-white p-4">
<h1 className="pb-4 text-2xl">Tags</h1>
{Object.entries(categories)
.sort((a, b) => (a[0] === "Other" ? 1 : b[1].count - a[1].count))
.map(([category, row]) => (
<div key={category} className="py-4">
<details open>
<summary key={category}>
{category} ({row?.count})
</summary>
<TagsWithCount tags={row?.tags} />
</details>
</div>
))}
</main>
)
}
export const pageQuery = graphql`
query Tags {
site {
siteMetadata {
title
}
}
allMarkdownRemark(limit: 2000) {
group(field: { frontmatter: { tags: SELECT } }) {
fieldValue
totalCount
}
}
allCategoriesJson(limit: 1000) {
edges {
node {
name
tags
}
}
}
}
`
export default TagsPage
GraphQLクエリ
- allMarkdownRemarkのgroupでタグごとの件数を取得
- allCategoriesJsonでマッピングのリストを取得
集計
上記で取得したデータを元に愚直に集計するだけ
まだいくつか思うところはあるがタグだけが羅列されているよりかはいい感じになった

実装PullRequestは下記