'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 an index.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 that allMdx and mdx are used in the basic tutorial.
  • Not need to tap into the createPages hook in gatsby-node.js because the gatsby-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