'How to create multiple node types with mdx in Gatsby?
I am trying to create multiple content types in Gatsby using mdx (instead of remark). I am having trouble sifting through old methods of handling remark and new methods with mdx. I have seen multiple instances of allXYZ
root nodes being queried in various tutorials, but I am having trouble understanding how those were created in the first place. I know in some cases Gatsby or plugins create these nodes (allMdx
, allMarkdownRemark
, allFile
, etc), but I want to learn how to create something like allPosts
or allProjects
with relevant field nodes for that content type myself.
My end goal is to achieve the following:
- Have mdx content stored in different folders (
posts
,projects
,pages
) that indicate their content type. - Have a matching folder structure in
src/pages
with anindex.js
file in each (the landing page for posts, projects, or pages) and a template file using the new syntax{type.field}.js
. - Be able to query
allPosts
/allProjects
/allPages
in the respective index.js files and use those types for the template file ({Post.slug}.js
,{Project.slug}.js
, etc) - The template file would query the child node (
post
,project
,page
) the same way thatallMdx
andmdx
are used in the basic tutorial. - Not need to tap into the
createPages
hook ingatsby-node.js
because thegatsby-source-filesystem
should do it for me with the above structure.
I found this stackoverflow post that posed a similar question, but the answer seems to imply these custom nodes (allPosts
, etc) should be automatically created when you setup the plugin options like this:
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/content/posts`,
},
},
That does not work for me however. When I use the __graphql
interface, these nodes don't exist, and if I try to query them anyways, I get an error (note: I have tried naming the template file using projects
, Project
, project
, etc. without success):
PageCreator: Tried to create pages from the collection builder.
Unfortunately, the query came back empty. There may be an error in your query:
Cannot query field "allProjects" on type "Query".
File: src/pages/projects/{projects.slug}.js
I also found this Gatsby guide which seems to address part of my question, but I don't understand how to source my data locally instead of through the API requests they are using. I also think this might be overcomplicating something very simple that should work natively with the mdx and filesystem plugins? Not sure!
I feel like I am missing something very basic. I am still new to Gatsby, so it's completely possible I am wrong in thinking this will work how I want it to, but I have spent hours trying to figure this out and think it's time to finally ask for help lol.
Any advice is appreciated!!
Solution 1:[1]
You create your own custom types using onCreateNode
https://www.gatsbyjs.com/docs/reference/config-files/gatsby-node/#onCreateNode
https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization/
My solution is to use fileAbsolutePath to determine whether the .mdx file is a page or post etc. based on the folders that my files are located in.
if (node.internal.type == 'Mdx' && node.fileAbsolutePath.indexOf('pages') !== -1) {
actions.createNode({
id: createNodeId(`MdxPage-${node.id}`),
parent: node.id,
internal: {
type: `MdxPage${node.internal.type}`,
contentDigest: node.internal.contentDigest,
},
})
}
Solution 2:[2]
You can try this Gatsby plugin https://www.gatsbyjs.com/plugins/gatsby-plugin-mdx-source-name/?=mdx
When do I use this plugin? This plugin is very useful if you are using multiple instances of gatsby-source-filesystem as it will allow you to query the name field from the source plugin on your Mdx nodes.
plugins: [
`gatsby-plugin-mdx-source-name` ,
{
resolve: `gatsby-source-filesystem` ,
options: {
path: `${__dirname}/src/blog` ,
name: `blog` // this name will be added to the Mdx nodes
}
}
]
The source name will now be available to query via GraphQL:
const query = graphql`
query {
allMdx(){
nodes {
id
fields {
source
}
}
}
}`
For example, if you wanted to filter by this new source field on page creation, you could do the following:
// gatsby-node.js
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions
// query for all Mdx pages
const query = graphql(`
query {
allMdx(){
nodes {
fields {
source
}
frontmatter {
slug
}
}
}
}
`)
return query.then(result => {
// filter by source name "blog"
const posts = result.data.allMdx.nodes.filter(node => node.fields.source === 'blog')
posts.forEach(node => {
createPage({
path: `/blog/${node.frontmatter.slug}`,
component: path.resolve('src/templates/blog-post.js'),
context: {
slug: node.frontmatter.slug
}
})
})
})
}
Solution 3:[3]
Another option is doing the folder structure as you described and than using allMdx to filter based on fileAbsolutePath
:
allMdx(
sort: { fields: frontmatter___date, order: DESC }
filter: { fileAbsolutePath: {regex: "/content/projects/"} }
)
or if you'd want to have multiple calls to allMdx in the same query that using an alias:
projects: allMdx(
filter: { fileAbsolutePath: {regex: "/content/posts/"} }
) { ... }
posts: allMdx(
filter: { fileAbsolutePath: {regex: "/content/projects/"} }
) { ... }
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Peter S |
Solution 2 | Andre Bellafronte |
Solution 3 | Rwzr Q |