Banner Image

Creating Pages From Markdown With GraphQL

Series:Building A Powerful Blog


Hello and welcome to the fourth post in my series on Building a Powerful Blog!🎉 So far, you've created your Gatsby site and deployed it for the world to see using Netlify. You've also learned about the power of Markdown for writing your own blog posts and have stored and retrieved it with GraphQL. If you haven't already, I would recommend going back to the first post of the series, Creating a Gatsby Blog Site, to get caught up before continuing. Now it's time to pull it all together and learn how to create reusable templates to display your blog post, and the many to come, on your own website. So what are we waiting for? Let's get started!👏


Creating Your Blog Post Pages

There are a few steps that will need to be taken to retrieve the content from your Markdown files. We'll first start by creating a template file so that the server has something to build your blog page off of. We'll then move on to creating a file in the root of your directory, which will run during the process of building your site. This file will create the separate URLs that you can navigate to from your site for each Markdown file. Then, we'll update the template file to display some of the content from your blog post in a nicely formatted view.

To get this started, go to the "src" folder of your codebase and create a folder titled "templates". Within this new folder, created a file titled "blog-post.js" so that your directory looks similar to the following:

+-- src
|   +-- pages
|   +-- posts
|   +-- templates
|   |   +-- blog-post.js
+-- gatsby-config.js

Within the newly created "blog-post.js" file, we're going to import React, create a quick function to return an h1 element, and export that function for use during the Gatsby build process. The code is as follows:

import React from "react"

const BlogPost = () => {
  return (
    <h1>Hello World!</h1>
  )
}

export default BlogPost

If you started up your local server right now and tried to view this page, you would be unable to do so. The reason being that Gatsby doesn't yet know how to handle this file! For this, we will need to create another file titled "gatsby-node.js" in the root of your directory. This file will implement the createPages API from Gatsby Node APIs and create dynamic blog pages for you. Let's first fetch your blog post data from the GraphQL data layer:

exports.createPages = async ({ actions, graphql, reporter }) => {
  const result = await graphql(`
    {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              path
            }
          }
        }
      }
    }
  `)
}

Here, we're using a similar query to the one you've seen in the last section of this post. We've removed the sort and limit fields though as they're currently not needed. This query will return all Markdown files that you have set the gatsby-source-filesystem plugin to point to in your "gatsby-config.js" file. This looks good right now, but what if there was an error when trying to run the query? That's where the following code comes into play:

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

  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }
}

With these few lines of code, you're able to report out that there was an error while running the query, and exit out of the createPages function so that you're entire site doesn't crash. In all my time of working with Gatsby, I've never seen this issue occur. Nevertheless though, it's good to have just in case. Now that we have the result from your data layer, how to we tell Gatsby to create the necessary pages using your template file? Look no further than the below snippet of code:

const path = require("path")

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions
  ...
    
  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.frontmatter.path,
      component: path.resolve(`src/templates/blog-post.js`),
      context: {}
    })
  })
}

We need to require the Path module from Node.js to provide us with a way of working with directories and file paths. Be sure to require that at the start of the file. We'll also need to extract the createPage method from the actions object and assign it to a variable. This should be done immediately at the start of your arrow function. Finally, towards the end of the arrow function, we're utilizing a forEach loop to iterate over each edge, or "post", and call the createPage method. This method will be called with an object consisting of:

  • the path to use for the URL,
  • the component to build the page with,
  • and any additional context.

This additional context field is useful if there's extra data that needs to be provided to the component itself but, for now, we'll go ahead and leave it empty. Below is the full snippet of code for you to check your "gatsby-node.js" file against mine:

const path = require("path")

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

  const result = await graphql(`
    {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              path
            }
          }
        }
      }
    }
  `)

  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }
  
  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.frontmatter.path,
      component: path.resolve(`src/templates/blog-post.js`),
      context: {}
    })
  })
}

That's it! Our site will now create a page for every node, i.e. Markdown file, it finds within your filesystem. As long as those files have a path within their frontmatter, you'll be able to navigate to that page and see the h1 we added as a placeholder. Let's go ahead and give it a try by stopping your server with ctrl+c and restarting it with npm run develop. Once the server is running, go to your web browser and navigate to http://localhost:8000. You should see the landing page of the Gatsby site you originally built. From here, add on a forward slash to the URL, followed by whatever you entered in the path field of your YAML frontmatter. For me, that's going to look like http://localhost:8000/my-first-blog-post and the page should look like this:

Screenshot of the placeholder page generated during the build of your Gatsby site

What a rush! We now have a site with dynamically generated URLs for any blog posts you add to the "posts" folder. While we're only displaying a placeholder for your blog post, this is just the beginning of what we can do for your blog site! If you would like to learn more about the power of Gatsby Node APIs while we wait for the next post to be released, I would highly recommend reading through their reference documentation.


In the next post, we'll continue working in the template file to retrieve the rest of the content from your blog post. This will help keep your code clean and organized, while setting us up nicely to display to content on your web pages. I hope you enjoyed this post and found it's content helpful as you continue your journey through web development! The links to my social media accounts can be found on the contact page of my website. Please feel free to share any of your own experiences on working with the Gatsby Node APIs, general questions and comments, or even other topics you would like me to write about. Thank you and I look forward to hearing from you!👋