Building a blog with Gatsby and MDX
2020-08-26 by Adam Goth · 5 min read
I've long considered trying out blogging. Now, I'm finally giving it a go.
This first post is all about how I built this blog feature into my site using Gatsby and MDX. For background context, I built this site about two years ago using Gatsby. Given that it is essentially a single landing page with various social links, using Gatsby wasn't entirely necessary, but I had been looking for an excuse to try it out. Fast forward two years, I've decided to make further use of Gatsby and leverage it to add a blog to the site. Here's how I went about it.
Overview
The basic idea behind this project is to use Gatsby plugins and APIs to dynamically generate blog pages from MDX files within our project. After our project is configured, adding a new blog post will be as easy as creating a new MDX file. The plugins we'll use include the gatsby-source-filesystem plugin and gatsby-plugin-mdx along with the createPages Gatsby Node API. After setting those up, we'll then take a brief look at how to template our posts and style our MDX elements. So how does all this work? Let's jump in.
Note: This blog post assumes basic Gatsby and React knowledge. If you are unfamiliar with Gatsby, there is a great tutorial series available on Gatsby's site to get you up to speed. Additionally, I will not be setting the project up step by step, but will provide enough information that you could easily integrate a similar blog feature into any existing Gatsby project.
Setting up
Our first requirement is the
gatsby-source-filesystem
plugin. What this plugin allows us to do is to use our project's filesystem as a
source for data in our application. We will create a posts
directory that will
store our MDX files. We will then use gatsby-source-filesystem
to query for
those files and convert the content into blog posts.
Our second requirement is going to be the gatsby-plugin-mdx plugin. This is the official integration for using MDX within Gatsby. If you are unfamiliar with MDX, it is essentially a file format that allows you to combine standard Markdown with JSX. I won't go into much detail but it is pretty neat, especially if you are a React developer and are used to writing JSX. You can read more about MDX here. This is the file format we'll use to write our posts.
After
installing
both plugins, we can configure them in our gatsby-config.js
file as follows.
1module.exports = {2 ...,3 plugins: [4 {5 resolve: `gatsby-source-filesystem`,6 options: {7 name: `posts`,8 path: `${__dirname}/src/posts/`,9 },10 },11 `gatsby-plugin-mdx`,12 ],13}
The path
value here is src/posts/
which is where I will store my MDX files.
This configuration tells Gatsby to query this particular directory for data.
Now that we have our plugins configured, our project is ready to query for MDX files and turn them into blog pages. But just how do we tell it to do that?
The answer is Gatsby's
createPages API. If you
are familiar with Gatsby, you know that by default, Gatsby core creates pages
for any React files that it finds in the src/pages/
directory. Similarly, this
API allows us to instruct Gatsby to create additional pages based on the
criteria we specify. When all is said and done, our gatsby-node.js
file
(located in the project's root directory) will look as follows:
1const path = require("path")23exports.createPages = async ({ graphql, actions, reporter }) => {4 // destructure the createPage function from the actions object5 const { createPage } = actions67 const result = await graphql(`8 query {9 allMdx {10 edges {11 node {12 id13 frontmatter {14 slug15 }16 }17 }18 }19 }20 `)2122 if (result.errors) {23 reporter.panicOnBuild('🚨 ERROR: Loading "createPages" query')24 }2526 // create blog post pages27 const posts = result.data.allMdx.edges2829 // call `createPage` for each result30 posts.forEach(({ node }, index) => {31 createPage({32 path: node.frontmatter.slug,33 component: path.resolve(`./src/components/posts-page-layout.js`),34 // you can use the values in this context in35 // our page layout component36 context: { id: node.id },37 })38 })39}
In the code above, we first query our filesystem for MDX files starting on line 7. These will be located in the directory we specified in our earlier configuration. Each node in our query results is an MDX file that was found.
Then starting on line 30, for each MDX file (or "post"), we call createPage
,
passing it a path
value which will serve as our post's URL, a component
value which will be used as our page template, and a context
object which can
hold additional data to be made available to us within our template component.
You may have noticed that on line 13, within each file result, we expect
something called frontmatter
.
Frontmatter
is a set of key-value pairs that can be used to provide additional data about
the file. In our case, we're going to use frontmatter for storing information
about the blog, including details such as our posts title, slug, date, author,
and more. Frontmatter is denoted in a Markdown file by three dashes at the start
and end of a block.
Creating posts from MDX files
Now that the project is configured to dynamically create pages from MDX files,
we need to create our MDX file within the directory we specified our
gatsby-source-filesystem
configuration (src/posts/
). If you are using a
slug
frontmatter key-value pair for your path
value as we are, the name of
the file is not so important, as long as it is an MDX file. For consistency, I
will give it the same name as the slug
(src/posts/building-a-blog-with-gatsby-and-mdx.mdx
). Make special note of the
frontmatter at the top of the file, located between the ---
lines. The slug
value is what will make your post available at http://<yourdomain>/<slug>
. We
will make use of the rest of the frontmatter data in our page template.
1---2title: Building a blog with Gatsby and MDX3slug: building-a-blog-with-gatsby-and-mdx4author: Adam Goth5---67Blog content here...
The last missing piece that our createPages
function expects is the template
component that we specified as the component
value in our createPage
call.
The value we passed is ./src/components/posts-page-layout.js
, so let's go
ahead and create that file.
1import React from "react"2import { graphql } from "gatsby"3import { MDXProvider } from "@mdx-js/react"4import { MDXRenderer } from "gatsby-plugin-mdx"5import Layout from "../components/layout"6import components from "./mdxComponents"78export default function PageTemplate({ data: { mdx } }) {9 return (10 <Layout>11 <div style={{ padding: "0 1rem", marginBottom: "10rem" }}>12 <h1>{mdx.frontmatter.title}</h1>13 <h414 style={{15 color: "gray",16 fontWeight: "normal",17 }}18 >{`${mdx.frontmatter.date} by ${mdx.frontmatter.author}`}</h4>19 <MDXProvider components={components}>20 <MDXRenderer>{mdx.body}</MDXRenderer>21 </MDXProvider>22 </div>23 </Layout>24 )25}2627export const pageQuery = graphql`28 query BlogPostQuery($id: String) {29 mdx(id: { eq: $id }) {30 id31 body32 frontmatter {33 title34 date35 author36 }37 }38 }39`
There are a few things to make note of here. If you are familiar with Gatsby and
React, nothing should look too out of the ordinary here. Starting on line 27, we
are using a graphQL query called BlogPostQuery
to query our filesystem for an
MDX file with the matching id
. The id
is generated in our createPages
function and is passed via the context
parameter to our page template
component. From the results, we get our body
and frontmatter
data for the
blog post from our MDX file. We can then access the query data in our component
via our component's props (see line 8 above).
Styling MDX content
If we wanted to render the body and frontmatter data as plain HTML elements
within our JSX, we could go ahead and do that within our PageTemplate
component above. In fact, that is how the title is being rendered on line 12.
But @mdx-js/react
and gatsby-plugin-mdx
provide us with <MDXProvider />
and <MDXRenderer />
components. These components allow us to customize the
style of our MDX content. Let's take a look at how.
In the above code block, we're importing components
from './mdxComponents'
and passing it to <MDXProvider />
. This components
prop on <MDXProvider />
allows us to pass customized components for each type of Markdown element we may
expect to render.
For example, in Markdown and MDX, a standard line of text gets rendered as a
paragraph (<p>
) element. But for my posts, I want my paragraph elements to
contain a certain line height different from the default. I also want to provide
my own margin and width properties. The way this can be accomplished is by
creating a custom paragraph
MDX component and passing it into the components
prop to MDXProvider />
.
In my src/components/mdxComponents
directory, I have a paragraph.js
file
that looks as follows:
1import React from "react"23export default ({ children }) => (4 <p style={{ lineHeight: "1.7", maxWidth: "750px", margin: "30px auto" }}>5 {children}6 </p>7)
In the same src/components/mdxComponents
directory, I also have an index.js
file which exports the component from paragraph.js
as well as all the other
elements I have created customized MDX components for. I then import that as an
object named components
and pass it to <MDXProvider />
. The body
of our
MDX file data is then passed as the children
prop to <MDXRenderer />
on line
20, which then makes use of these components when it renders the MDX content. In
addition to the paragraph element, a full list of other elements that can be
customized in MDX can be found
here.
Wrapping up
That concludes the process for how I've built out a blog feature, as well as the
very blog post you are reading. Using Gatsby plugins gatsby-source-filesystem
and gatsby-plugin-mdx
, along with Gatsby's createPages
API, we are able to
use MDX files to dynamically generate pages for blog posts. Going forward, for
my next post, all I will need to do is write the new MDX file and add it to
src/posts/
. I eventually plan on adding additional features such as tags,
categories, search, and who knows what else. So if you are interested in that as
well, stay tuned!
If you are interested in viewing the source code for the project, the repository for my site is publicly available on Github.
If you enjoyed this post or found it useful, please consider sharing it on Twitter.
If you want to stay updated on new posts, follow me on Twitter.
If you have any questions, comments, or just want to say hello, send me a message.
Thanks for reading!