Using Templates & Markdown to Create Pages

footprint on escalator

Your first step in this final post on Gatsby is to check out the previous posts to get caught up. Those posts can be found here, here and here.

To get this templated content rolling first we will need to add some handy NPM packages and configuration to the gatsby-config.js file. In the plugins array we will want to add the following:

  {
    resolve: "gatsby-plugin-mdx",
    options: {
      extensions: [`.md`, `.mdx`],
    },
  },
  {
    resolve: "gatsby-source-filesystem",
    options: {
      name: "pageContent",
      path: `${__dirname}/src/pageContent`,
    },
  },

The first addition is the ability to read the markdown files we will be using as the body of our content for the pages. The second is to configure Gatsby to read files from a disk in a particular directory location. Next, we will want to install the appropriate NPM packages to support this configuration. Execute npm install gatsby-plugin-mdx and npm install gatsby-source-filesystem. Then attempt to restart the Gatsby development server. At the time of writing this, I ran into issues of @mdx-js/mdx and @mdx-js/react packages missing. If that is the same for you execute npm install @mdx-js/mdx and npm install @mdx-js/react. Then attempt to start the development server again.

Next, we will want to create a new folder for our content called pageContent under the src directory. Just as it appears in the gatsby-config.js file above. Nothing is preventing you from changing the name of the directory, or the location. The requirement is that the new directory and the configuration align. Then let us create a file page3.md with the below contents:

  ---
  title: Page 3 Title
  ---
  Here is some page 3 content

Notice the contents within the bounding ---? That is called frontmatter, it allows the markdown file to contain metadata about the content. This can be nearly anything you want, even if it is a complex object. If it can be written in YML syntax, it can be read by the parser. So we now have the tools and the content for the page. How about we create a React template to put the content into for presentation? Create a new directory under src named templates. In that folder create a new file pageTemplate.js with the contents of:

import * as React from "react";
import { MDXRenderer } from "gatsby-plugin-mdx";

const PageTemplate = ({ pageContext }) => {
  const { pageContent } = pageContext;
  return (
    <main>
      <div>{pageContent.frontmatter.title}</div>
      <MDXRenderer>{pageContent.body}</MDXRenderer>
    </main>
  );
};

export default PageTemplate;

What is happening in this React component? On the constructor, we are taking in a pageContext off the props, which we are then driving a level deeper and using the pageContent object. From there, everything in the JSX is dynamic based on this object passed into the component. Finally, we need to hook into the Gatsby build cycle to marry all these pieces together, into a final working system.

Create a sibling to the gatsby-config.js file named gatsby-node.js with these contents:

const path = require(`path`);

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

  const pageTemplate = path.resolve(`src/templates/pageTemplate.js`);

  const result = await graphql(`
    {
      allMdx(limit: 1000) {
        edges {
          node {
            id
            body
            slug
            frontmatter {
              title
            }
          }
        }
      }
    }
  `);

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

  result.data.allMdx.edges.forEach((edge) => {
    createPage({
      path: `/${edge.node.slug}`,
      component: pageTemplate,
      context: {
        pageContent: edge.node,
      },
    });
  });
};

In the first part of this file, we are getting everything set up to generate pages of a specific template. We access our createPage action, resolve the template file from disc, query the data layer for markdown files we are interested in, then finally some error handling. With all this information, we iterate over each result in the query, binding the content from the markdown file to a page template (in our case a simple react component). Passing in the desired relative path and the content of pageContent. Once this file is saved, you will be required to restart the development server. With the server restarted, it is time to check out our handy work. Navigate to localhost:{port}/page3 and you will see the content within the markdown file page3.md.

So to recap, in this series of posts, you have learned:

  1. What Gatsby.js is
  2. How to create a new Gatsby.js site
  3. How to add content pages
  4. How to template the creation of content pages

All said and done, the power of this framework allows a user to go from no knowledge to working site FAST. Dare I say within an hour this can all be done? It probably won't have a great appearance in that amount of time, but who cares. This is static site development at record speed with a template engine. That is pretty impressive.

If you are looking for more on this topic, the documentation out there for Gatsby.js is great! I cannot recommend it enough. There are lots of blogs out there that have written solutions to the more difficult implementations out there as well. Also, check out the plugin library, it's HUGE. There are tons of solutions in the library that you no longer have to write yourself, just install and configure. Things like sitemaps and robots.txt files, can all be handled for you.

I hope you found this series as fun to read and follow-through, as I had fun writing it. If you have feedback on anything, please message me via LinkedIn.