How to create a text/markdown node to process content using Gatsby

Table of contents

Sometimes when building Gatsby sites, there is a need to consume and transform markdown content from a source that is not a markdown file. In our case, it was a JSON file containing markdown data.

In this post, we will show you how you can consume markdown content from a JSON file to create a text/markdown node. Finally, I will demonstrate how to render that content using MDX to override the output of heading tags.

Adding gatsby-transformer-json dependency using npm

Run this on your Gatsby project within the root directory.

npm install --save gatsby-transformer-json

Configuring the plugins

For the purpose of this example, we are going to have a releases.json file located at src/data.

[
  {   
    "id": "12849491",
    "tag_name": "1.9.0",
    "body": "### Lorem ipsum"
  },
   {

    "id": "11400948",
    "tag_name": "1.8.1",
    "body": "### Bar baz"
  }
]


For this reason, we need to have gatsby-source-filesystem installed and configured to point to a path where our JSON files are located.

plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/data`,
        name: `data`,
      },
    },
    `gatsby-transformer-json`,
]

Creating the text/markdown node

Now, add the following code to onCreateNode at the gatsby-node.js file.

According to the GatsbyJS documentation, the onCreateNode is called when a new node is created. Plugins wishing to extend or transform nodes created by other plugins should implement this API.

See also the documentation for createNode and createNodeField

  const { createNode, createNodeField } = actions  

  // Releases Nodes
  if (node.internal.type === `ReleasesJson`) {
    // Add text/markdown node children to Release node
    const textNode = {
      id: `${node.id}-MarkdownBody`,
      parent: node.id,
      dir: path.resolve("./"),
      internal: {
        type: `${node.internal.type}MarkdownBody`,
        mediaType: "text/markdown",
        content: node.body,
        contentDigest: digest(node.body),
      },
    }
    createNode(textNode)

    // Create markdownBody___NODE field
    createNodeField({
      node,
      name: "markdownBody___NODE",
      value: textNode.id,
    })
  }


What is this code doing?

Validating node.internal.type is equal to ReleasesJson to affect only this node types.

Creating a textNode JSON object and defining among other values:

  • id as ${node.id}-MarkdownBody.
  • parent node as current node.id.
  • dir value as path.resolve("./") to make Gatsby treat markdown content as it comes from a file and you can use relative paths on images.
  • internal.type equals to ${node.internal.type}MarkdownBody.
  • internal.mediaType equals to text/markdown.
  • content equals to current node markdown field node.body.

Using createNode function to create a new GraphQL node.

Using createNodeField function to create a new field on current node passing the parameters: node, name and file value. Using previously created textNode.id as value on the value parameter.

By doing this, we will enable Gatsby to take care of processing and transforming the content for our new markdown node.

Override headings using MDX and MDXRenderer

In this particular case, we want to override the rendering of our h1, h2, and h3headings. This step allows us to avoid any additional processing from the gatsby-remark-slug plugin.

The src/components/releaseHeadlines.js file:

import React from "react"

export const headline1 = props => {
  return <h1 className="toc-ignore">{props.children}</h1>
}

export const headline2 = props => {
  return <h2 className="toc-ignore">{props.children}</h2>
}

export const headline3 = props => {
  return <h3 className="toc-ignore">{props.children}</h3>
}


This component is overriding any markup on h1, h2, and h3 components and setting the custom class toc-ignore to avoid these headings from being processed by javascript plugins.

The src/components/releases.js file:

import React from "react"
import { StaticQuery, graphql } from "gatsby"
import MDXRenderer from "gatsby-mdx/mdx-renderer"
import { MDXProvider } from "@mdx-js/react"

import { headline1, headline2, headline3 } from "./releaseHeadlines"

const shortcodes = {
  h1: headline1,
  h2: headline2,
  h3: headline3,
}

const Releases = ({ data }) => (
  <>
    {data.allReleasesJson.edges.map((release, i) => {
      return (
        <div key={i}>
          <h3 className="toc-ignore">{release.node.tag_name}</h3>
          <MDXProvider components={shortcodes}>
            <MDXRenderer>
              {release.node.fields.markdownBody.childMdx.code.body}
            </MDXRenderer>
          </MDXProvider>
          <hr />
        </div>
      )
    })}

)

export default props => (
  <StaticQuery
    query={graphql`
      query {
        allReleasesJson(
          sort: { fields: [tag_name], order: DESC }
        ) {
          edges {
            node {
              id
              tag_name
              fields {
                markdownBody {
                  childMdx {
                    code {
                      body
                    }
                  }
                }
              }
            }
          }
        }
      }
    `}
    render={data => <Releases data={data} {...props} />}
  />
)


What is this code doing?

Using GraphQL and the allReleasesJson query to pull all of the registered nodes from our releases.json file.

Providing an array of components we want to override and passing them to the MDXProvider component.

Using the MDXProvider and MDXRenderer to render the content on release.node.fields.markdownBody.childMdx.code.body. The markdownBodyfield available as fields children is the field we created with the code that we added to onCreateNode within the gatsby-node.js file.

For a piece of more detailed information about how to add and configure MDX dependencies, read the previous post on of the series How to embed React components in markdown using Gatsby, MDX, and short-codes.

Are you interested in knowing more about GatsbyJS?

This series does not end here, so we invite you to visit our site frequently as we continue. We will be sharing some nice tips & tricks that we have learned during the development of this project. We will be sharing with you:

  • How to query all posts by a specific author when multiple authors are listed in the front-matter field of your markdown file.
  • How to load templates based on your front-matter field.
  • How to create page navigation based on directory structure. How to use the parent directory as the base. Finally, how to show all of the children files as clickable links to separate pages.

So stay tuned and keep on learning with us. If you want to learn more about what we do and how we may help your business, don’t think about it twice and reach out to us!