(Gatsby) Displaying frontmatter data in MDX component

(Gatsby) Displaying frontmatter data in MDX component


0

I’m trying to create a simple Byline component to use in my MDX posts that displays the site author with a href="mailto:[email protected]" and a time tag with the date the post is given in its frontmatter.

I’ve tried following this Gatsby tutorial to learn how to query frontmatter through GraphQL in a component.

I don’t really know React nor GraphQL but I understand that the first one (author) is queried through site.siteMetadata.author.name (I think?).

As for the second one, since it uses the post’s frontmatter, I would assume I’m supposed to use mdx.frontmatter.date.

I’m not getting any of this to work. I get these errors:

Cannot read properties of undefined (reading ‘site’)

Cannot read properties of undefined (reading ‘mdx’)

I also get this error:

There was an error in your GraphQL query:

Variable "$id" is not defined by operation "BylineQuery".

GraphQL request:10:19
 9 |     }
10 |     mdx(id: { eq: $id }) {
   |                   ^
11 |       frontmatter {

GraphQL request:2:3
1 |
2 |   query BylineQuery {
  |   ^
3 |     site {

This is the complete component:

import * as React from 'react'
import { graphql } from 'gatsby'

const Byline = ({data }) => {
  return (
    <div className="byline">
      <address>Av <a href="mailto:[email protected]" rel="author">{data.site.siteMetadata.author.name}</a></address>
      <time dateTime={data.mdx.frontmatter.computerDate}>{data.mdx.frontmatter.humanDate}</time>
    </div>
  )
}

export const query = graphql`
  query BylineQuery {
    site {
      siteMetadata {
        author {
          name
        }
      }
    }
    mdx(id: { eq: $id }) {
      frontmatter {
        computerDate: date(formatString: "YYYY-MM-DD")
        humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
      }
    }
  }
`

export default Byline

gatsby-node.js:

const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions

  // Define a template for blog post
  const blogPost = path.resolve(`./src/templates/blog-post.js`)

  // Get all markdown blog posts sorted by date
  const result = await graphql(
    `
      {
        allMdx(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          nodes {
            id
            fields {
              slug
            }
          }
        }
      }
    `
  )

  if (result.errors) {
    reporter.panicOnBuild(
      `There was an error loading your blog posts`,
      result.errors
    )
    return
  }

  const posts = result.data.allMdx.nodes

  // Create blog posts pages
  // But only if there's at least one markdown file found at "content/blog" (defined in gatsby-config.js)
  // `context` is available in the template as a prop and as a variable in GraphQL

  if (posts.length > 0) {
    posts.forEach((post, index) => {
      const previousPostId = index === 0 ? null : posts[index - 1].id
      const nextPostId = index === posts.length - 1 ? null : posts[index + 1].id

      createPage({
        path: post.fields.slug,
        component: blogPost,
        context: {
          id: post.id,
          previousPostId,
          nextPostId,
        },
      })
    })
  }
}

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `Mdx`) {
    const value = createFilePath({ node, getNode })

    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions

 
  createTypes(`
    type SiteSiteMetadata {
      author: Author
      siteUrl: String
      social: Social
    }

    type Author {
      name: String
      summary: String
    }

    type Social {
      twitter: String
      instagram: String
      mail: String
    }

    type MarkdownRemark implements Node {
      frontmatter: Frontmatter
      fields: Fields
    }

    type Frontmatter {
      title: String
      description: String
      date: Date @dateformat
    }

    type Fields {
      slug: String
    }
  `)
}

Byline.js:

(Currently just testing.)

(Located in components directory.)

import React from "react"
import { graphql } from "gatsby"

export default function Byline({ data: { mdx } }) {
  return (
    <div>
      <h1>{mdx.frontmatter.title}</h1>
    </div>
  )
}

export const pageQuery = graphql`
  query BylineQuery($id: String) {
    mdx(id: { eq: $id }) {
      id
      body
      frontmatter {
        title
      }
    }
  }
`

gatsby-config.js

module.exports = {
  siteMetadata: {
    title: `Magnus Kolstad`,
    author: {
      name: `Magnus Rengård Kolstad`,
      summary: `Summary`,
      description: "Artikler skrevet av Magnus Kolstad",
    },
    description: `Description`,
    siteUrl: `https://kolstadmagnus.no/`,
    social: {
      mail: `[email protected]`,
      instagram: `kolstadmagnus`,
      twitter: `KolstadMagnus`,
      youtube: `UC7QpsGiWwVc9lmnIJvA9OLA`
    },
  },
  plugins: [
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `blog`,
        path: `${__dirname}/content/blog`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `pages`,
        path: `${__dirname}/src/pages/`,
      }
    },
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        defaultLayouts: {
          default: require.resolve(`./src/components/layout.js`),
        },
        gatsbyRemarkPlugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: `900000000000`,
              linkImagesToOriginal: false,
              backgroundColor: `none`,
            },
          },
          {
            resolve: `gatsby-remark-responsive-iframe`,
            options: {
              wrapperStyle: `margin-bottom: 1.0725rem`,
            },
          },
          `gatsby-remark-prismjs`,
          `gatsby-remark-copy-linked-files`,
          `gatsby-remark-smartypants`,
        ],
        extensions: [`.md`, `.mdx`],
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Gatsby Starter Blog`,
        short_name: `GatsbyJS`,
        start_url: `/`,
        background_color: `#ffffff`,
        display: `minimal-ui`,
        icon: `src/images/gatsby-icon.png`,
      },
    },
    `gatsby-plugin-react-helmet`,
  ],
}

Share

2 Answers
2

Reset to default


0

Extending Hasan’s answer:

First of all, here you wrote a page query. Page query only works in
pages. Make sure your component is in the pages directory.

That is partially true. Page queries also work in templates, which means that they are also pages but they aren’t located in the src/pages folder but src/templates (assuming the guide’s project structure).

Despite the query looks as it should be:

export const query = graphql`
  query BylineQuery($id: String) {
    site {
      siteMetadata {
        author {
          name
        }
      }
    }
    mdx(id: { eq: $id }) {
      frontmatter {
        computerDate: date(formatString: "YYYY-MM-DD")
        humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
      }
    }
  }

Check it in the GraphiQL playground (localhost:8000/___graphql) with a hardcoded id if needed or changing the filter to another handy field.

The fact is that the key part is that you need to pass a id from your gatsby-node.js to the template using the context as it is inferred in the tutorial. For example::

posts.forEach(({ node }, index) => {
  createPage({
    path: node.fields.slug,
    component: path.resolve(`./src/templates/blog-post.js`),
    // values in the context object are passed in as variables to page queries
    context: {
      title: node.title,
      id: node.id
    },
  })
})

The title and id variable will be exposed in the blog-post.js template (under ./src/templates) and can be used to filter your posts (in this case) using those values.

Simplifying, gatsby-node.js creates your template pages (posts, etc) and to query the specific data for each post, you need to filter the data using a lifted context variable sent via context.

Share

5

  • To me, this example looks like just the thing I'm looking for: gatsbyjs.com/docs/mdx/programmatically-creating-pages/…. Yet I can't run the component—I keep getting the "Cannot read properties of undefined (reading 'mdx')" warning. What am I missing out on here? Do I have to do something with my gatsby-node.js file? If so, what precisely?

    – Magnus Kolstad

    Dec 28, 2021 at 16:57

  • Look closely, you are not lifting the $id in your query yet you're using it before so it breaks. You may be missing other configurations but you haven't share it. Add them in your question because it may be the source or the explanation of why the nodes are missing but certainly you have an issue in the query. share the gatsby-node.js too to see how are you generating the pages

    – Ferran Buireu

    Dec 28, 2021 at 18:01


  • Added gatsby-node.js and component.

    – Magnus Kolstad

    Dec 28, 2021 at 18:21

  • gatsby-config.js is still missing. Have you tried using query BylineQuery($id: String)? Have you tried the query in the GraphiQL playground? (hardcoding the id or using the title as a filter)

    – Ferran Buireu

    Dec 29, 2021 at 5:51


  • I added gatsby-config.js. Yes, query BylineQuery($id: String) is what I'm currently using in Byline.js. I will try messing around with GraphiQL later today.

    – Magnus Kolstad

    Dec 29, 2021 at 9:17


-1

First of all, here you wrote a page query. Page query only works in pages. Make sure your component is in the pages directory. And the error comes from your query where you used a dynamic argument "$id" but you didn’t pass the variable.

export const query = graphql`
  query BylineQuery($id: String) {
    site {
      siteMetadata {
        author {
          name
        }
      }
    }
    mdx(id: { eq: $id }) {
      frontmatter {
        computerDate: date(formatString: "YYYY-MM-DD")
        humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
      }
    }
  }
`

your query should be like this. Check the documentation ( https://www.gatsbyjs.com/docs/how-to/querying-data/page-query/ )

Share

0



Not the answer you're looking for? Browse other questions tagged

or ask your own question.

Leave a Reply

Your email address will not be published. Required fields are marked *