I have a development on Gatsby with allMdx. I created a "Category" taxonomy and to create a category page I used a file gatsby-node.js . There's such a code inside.
const _ = require("lodash")
const { transliterate } = require('./src/functions/transletter');
function dedupeCategories(allMdx) {
const uniqueCategories = new Set()
// Iterate over all articles
allMdx.edges.forEach(({ node }) => {
// Iterate over each category in an article
node.frontmatter.categories.forEach(category => {
uniqueCategories.add(category)
})
})
// Create new array with duplicates removed
return Array.from(uniqueCategories)
}
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
// Query markdown files including data from frontmatter
const { data: { allMdx } } = await graphql(`
query {
allMdx {
edges {
node {
id
frontmatter {
categories
tags
slug
}
}
}
}
}
`)
// Create array of every category without duplicates
const dedupedCategories = dedupeCategories(allMdx)
// Iterate over categories and create page for each
dedupedCategories.forEach(category => {
reporter.info(`Creating page: blog/category/${category}`)
createPage({
path: `blog/category/${_.kebabCase(transliterate(category))}`,
component: require.resolve("./src/templates/categories.js"),
// Create props for our CategoryList.js component
context: {
category,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.categories.includes(category)
})
.map(({node}) => node.id),
},
})
})
}
Now I want to create a "Tag" taconomy, but I can't figure out how to do it beautifully and briefly, what and where to add to the gatsby-node.js so that I have two taxonomies created that work the same way as one. It is clear that you can simply duplicate this code and write "tag" instead of "category", but this is not very nice.
Just in case, here is my template code category.js
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from '../components/layout'
import Seo from '../components/seo'
const CategoryList = ({ pageContext: { category }, data: { allMdx }, }) =>
(
<Layout pageTitle={category}>
{
allMdx.edges.map(({ node }) => {
return (
<article key={node.id}>
<h2>
<Link to={`/blog/${node.frontmatter.slug}`}>
{node.frontmatter.title}
</Link>
</h2>
<p>Posted: {node.frontmatter.date}</p>
<p>{node.excerpt}</p>
</article>
)
})
}
</Layout>
)
export const query = graphql`
query CategoryListQuery($ids: [String]!) {
allMdx (filter: { id: { in: $ids } }) {
edges {
node {
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
slug
}
id
excerpt
}
}
}
}
`
export const Head = ({ pageContext }) => (
<Seo
title={pageContext.category}
description={`Статьи из категории ${pageContext.category}`}
/>
)
export default CategoryList
I don't see anything wrong in your approach. In fact, is the way to go in terms of getting all tags and categories (hence a map
) and loop through them to create the pages, at least given your approach.
However, I think you can save some steps if you change your markdown structure: if each MDX has a key
attribute (or similar) containing the type of taxonomy it is (article, tag, category, page, etc) you can create more succinct GraphQL queries, hence you can save the filters.
For instance, in that way, you would be able to create a single query tags and categories:
const tagsQuery = await graphql(`
query getAllTags {
allTags: allMarkdownRemark (
filter: { frontmatter: { key: { in: ["tag"] }}}) {
edges {
node {
id
frontmatter {
name
slug
type
key
}
}
}
}
}
`);
Note: you can use eq
operator instead of in
. In this case the array will accept more types of tags like projectTags
, articleTags
, etc.
This allows you to create more specific approach because your data will contain allTags
and allCategories
, so you can create a general dedupeCategories
(which will be named dedupeMdx
) which no matter the input, will return a unique array (of tags
or categories
) because you don't care about the data, all MDX will have the same internal structure to the loop and the function can be agnostic to that.
Following that approach, you can omit the following filter
:
context: {
category,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.categories.includes(category)
})
.map(({node}) => node.id),
},
The filter
won't be necessary if you pass the dedupedCategories
array (and so with the tags) and use a filter GraphQL in the template query, which in fact is what you would do either way, so you are saving one step.
In other words, you create pages for each category (or tag), pass the array of categories via context and get, from allMdx
(filtered by the key
+ the unique array) the needed data.