Im trying to get up to speed on Gatsby and have great success with the demos, but am running into a wall with what I feel like is a relatively common and simple use case. I would like to have multiple content types that I can write in Markdown, each that has different Frontmatter, and each that has a different template.
For example, I would like a BlogPost content type and and a Project content type:
---
title: My Post
date: "2017-09-21"
---
This is my blog body
---
projectName: My Project
startDate: "2017-09-21"
endDate: "2017-10-21"
---
This is my project description
And then to have them render in the relevant Template, I had to do some hacky stuff in gatsby-node.js
using regular expressions:
const components = {
blog: `./src/templates/blog-post.js`,
projects: `./src/templates/project-post.js`,
}
exports.createPages = ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators
RE_DIR = new RegExp("\/pages\/([a-z]+)\/.*\.md$");
return new Promise((resolve, reject) => {
graphql(`
{
allMarkdownRemark {
edges {
node {
fileAbsolutePath
fields {
slug
}
}
}
}
}
`).then(result => {
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
// console.log(RE_DIR.exec(node.fileAbsolutePath))
const postType = RE_DIR.exec(node.fileAbsolutePath)[1]
if (postType) {
createPage({
path: node.fields.slug,
component: path.resolve(components[postType]),
context: {
// Data passed to context is available in page queries as GraphQL variables.
slug: node.fields.slug,
},
})
}
})
resolve()
})
})
};
The problem Im having now, is since the frontmatter is inconsistent, it appears GraphQL only picks up the frontmatter schema from one of the sources.
Is there an easier way to have multiple content types?
Adding my answer in which is based on @nicokant but seems to have changed a bit. I also use mdx here but just swap out for MarkdownRemark
if that is what you use:
Give each source a name option:
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/posts`,
name: 'post',
},
},
Then when the node is created, assign it a custom field:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark` || node.internal.type === `Mdx`) {
createNodeField({
name: `collection`,
node,
value: getNode(node.parent).sourceInstanceName
});
})
};
Then you can query it based on a custom field:
query {
allMdx(filter: { fields: { collection: { eq: "post"}}}) {
edges {
node {
fields {
collection
}
frontmatter {
title
}
}
}
}
}
Define different sources in gatsby-config
and place your contents in different directories like src/projects
and scr/blog-posts
{
resolve: `gatsby-source-filesystem`,
options: {
name: `project`,
path: `${__dirname}/src/project/`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/blog-posts/`,
},
},
then you can create a field type based on the source name in gatsby-node
exports.onCreateNode =({ node, getNode, boundActionCreators }) => {
if (node.internal.type === 'MarkdownRemark') {
const { createNodeField } = boundActionCreators;
node.collection = getNode(node.parent).sourceInstanceName;
}
}
now you can filter your graphql query for collection of contents and you can generate specific templates based on content type.
query postsOnly {
allMarkdownRemark(filter: { collection: { eq: "posts" } }) {
edges {
node {
id
collection
}
}
}
}
code is based on this comment on github issue tracker
note: you should not directly mutate node instance in gatsby-node
, but instead use createNodeField
. If you know how to filter in graphql using custom fields please add to this answer!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With