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 currentnode.id
.dir
value aspath.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 totext/markdown
.content
equals to current node markdown fieldnode.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 h3
headings. 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 markdownBody
field 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!