Building a Personal Site with Gatsby

Part 3: Generating Blog Posts with Markdown Files

January 11, 2019

Note: This is a 9 part post that begins here: Part 1: Introduction and Setup

In order to read files into Gatsby, you need to install gatsby-source-filesystem. You also need the gatsby-transformer-remark plugin in order to recognize and parse markdown files.

shell
npm install --save gatsby-source-filesystem gatsby-transformer-remark

and add both to your gatsby-config.js:

gatsby-config.js
module.exports = {
  siteMetadata: {
  // ...
  },
  plugins: [
    `gatsby-plugin-sass`,
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages`,
        name: 'markdown-pages',
      },
    },
  // ...
  ],
}

We're going to source our markdown files from src/pages and create a blog directory within that to hold our blog posts.

Create the src/pages/blog directory and add a file called hello-world.md with the following:

src/pages/blog/hello-world.md
---
path: /blog/hello-world
title: Hello World
date: 2018-12-29
---

This is my first blog post. Hello World!

The data within the --- dashes is the frontmatter, written in YAML and made available to the GraphQL API. The content after the dashes is converted to HTML.

Let's create a page template for our blog posts. Create a src/templates directory and a src/templates/postTemplate.js file. Inside postTemplate.js, add the following:

src/templates/postTemplate.js
import React from 'react';
import { graphql } from 'gatsby';

import Layout from '../components/layout';

const PostTemplate = ({ data }) => {
  const { markdownRemark } = data;
  const { frontmatter, html } = markdownRemark;
  return (
    <Layout>
      <section>
        <div>
          <h1>{frontmatter.title}</h1>
          <span>{frontmatter.date}</span>
        </div>
        <div dangerouslySetInnerHTML={{ __html: html }} />
      </section>
    </Layout>
  );
};

export default PostTemplate;

export const pageQuery = graphql`
  query($path: String!) {
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        path
        title
      }
    }
  }
`;

Line 24 makes a GraphQL query to read the markdown files, which are made available to our PostTemplate as data. You can see what is available to query in GraphiQL, which is available at http://localhost:8000/___graphql. If you're unsure which fields you can use, hitting ctrl + spacebar will list your available options.

To see what data is available to query, restart your server and then add the following query in GraphiQL. You should see all of your markdown files (which for now is our lone Hello World post).

{
  allMarkdownRemark {
    edges {
      node {
        frontmatter {
          title
          path
          date
        }
      }
    }
  }
}

Creating static pages with the Gatsby Node API

Gatsby provides a number of APIs that you can implement in your gatsby-node.js file, but the one we want to focus on is createPages. In gatsby-node.js:

gatsby-node.js
const path = require('path');
const { createFilePath } = require('gatsby-source-filesystem');

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

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

  return graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              path
            }
          }
        }
      }
    }
  `).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors);
    }

    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
      createPage({
        path: node.frontmatter.path,
        component: postTemplate,
        context: {}, // additional data can be passed via context
      });
    });
  });
};

In createPages, we're making a GraphQL query to get our markdown files and then using that data to generate pages using the postTemplate. Currently, we are specifiying custom paths from the blog post's frontmatter.

If you want to create the slugs programatically instead of specifying a path in the frontmatter of your markdown files (which the rest of these blog posts will do), you can use Gatsby's onCreateNode API. After createPages, add:

gatsby-node.js
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions;
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` });
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    });
  }
};

This creates a slug and assigns it to a new field called "slug", which can now be queried by GraphQL. Edit your createPages call:

gatsby-node.js
exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions;

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

  return graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {          node {            fields {              slug            }          }        }      }
    }
  `).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors);
    }

    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
      createPage({
        path: node.fields.slug,        component: postTemplate,        context: { slug: node.fields.slug }, // additional data can be passed via context      });
    });
  });
};

You also have to edit the PostTemplate graphql query, since we're not using the path variable anymore:

src/templates/postTemplate.js
export const pageQuery = graphql`
  query($slug: String!) {    markdownRemark(fields: { slug: { eq: $slug } }) {      html
      frontmatter {        date(formatString: "MMMM DD, YYYY")        title      }    }
  }
`;

If you restart your server, you should see a page at /blog/hello-world.