Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gatsby: Multiple Content Types

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:

BlogPost Content Type

---
title: My Post
date: "2017-09-21"
---

This is my blog body

Project Content Type

---
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?

like image 937
dvreed77 Avatar asked May 08 '18 21:05

dvreed77


2 Answers

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
        }
      }
    }
  }
}
like image 105
wesbos Avatar answered Nov 03 '22 11:11

wesbos


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!

like image 33
nicokant Avatar answered Nov 03 '22 11:11

nicokant