dvreed77
dvreed77

Reputation: 2387

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?

Upvotes: 9

Views: 2855

Answers (2)

nicokant
nicokant

Reputation: 493

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!

Upvotes: 7

wesbos
wesbos

Reputation: 26317

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

Upvotes: 9

Related Questions