Create a Gatsby site with multiple types of Markdown pages

In the previous blog post I explained the method we used to export the content of the old 7Sabores Drupal 7 site into Markup files. In this blog post I am going to review how these files became the content source of a new Gatsby site, replacing both the CMS and its database.

One of the best things about Gataby JS is that it offers the ability to quickly create pages from Markdown files. Only two plugins are needed for Gatsby to access the content of Markdown files.

Plugins

The first plugin that we need to install is gatsby-source-filesystem. With this plugin our Gatsby application will be able to use files stored in the local file system as data source. For each file, a File node is created that can be queried through GraphQL in allFile and File.

To be able to use this plugin we must install the plugin package using NPM (or Yarn).

npm install gatsby-source-filesystem

Once the package is installed we must configure the new plugin in the plugins section of the gatsby-config.js file of our application and specify the directory in which the files that we are going to use as content are stored.

module.exports = {
  plugins: [
    {
      resolve:  `gatsby-source-filesystem`,
      options: {
        path:  `${__dirname}/content/blog`,
        name: `blog`,
      },
    },
  ],
}

With that we can access to the information of the files from the GraphQL data layer, but to be able to access the Markdown content and display it on a page of our application we must use another plugin. This second plugin is the gatsby-transformer-remark and it is a Transformer plugin, that is, it “transforms” the data provided by a source plugin, in our case the gatsby-source-filesystem, and transforms them into new nodes and fields in the Data Layer.

The gatsby-transformer-remarkm plugin goes through all files with extension .md or .markdown inside the configured directory and transform them into new nodes of type MarkdownRemark. The Markdown content is parsed by the plugin and creates html and excerpt fields in the GraphQL data layer. The Frontmatter is also parsed and fields are created in GraphQL for them.

Install the plugin package.

npm install gatsby-transformer-remark

And then we configure it in gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve:  `gatsby-source-filesystem`,
      options: {
        path:  `${__dirname}/content/blog`,
        name: `blog`,
      },
    },
    `gatsby-transformer-remark`,  ],
}

Organizing the content

What we've seen so far is similar to what you'll find in any Gatsby JS guide that explains how to create pages from Markdown content, but those guides probably say that the next step is to create a file with the following name to create the pages.

src/pages/{MarkdownRemark.frontmatter__slug}.js

That's a simple solution and it works great if all pages generated from markdown files on the site have the same format. On this particular site we have files with different fields on the Frontmatter and we want to have more control over how each of the page types are going to be displayed.

By creating directories in the file system we can separate the different types of pages that are going to be generated from markdown files. To organize all the content of the site I have created a folder called “content” inside it there is a folders for each type of page and an additional folder called assets that contains all the images that are embedded in the content of markdown pages.

To separate the different types of content I have configured each folder separately in gatsby-config.js.

    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/blog`,
        name: `blog`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/entrevista`,
        name: `interview`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/lesson`,
        name: `lesson`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/glosario`,
        name: `glosary`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/user`,
        name: `user`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `assets`,
        path: `${__dirname}/content/assets`,
      },
    },

I know that it's possible to group all these configurations into a single one for the content folder and include everything inside it at once, but I wanted to have control over each new type of content that was added to the site.

    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/content`,
      },
    },

Templates para las páginas

The next step is to create the different templates that we need for our pages.

These are React components inside .js files (or .tsx if you're using TypeScript). To keep things tidy we are going to save them inside the src/templates folder to separate them from other components.

Below is a very simple example of a template for blog posts. This template is just a regular React component that will be used when generating the page.

import * as React from "react"
import { graphql } from "gatsby"

const BlogPostTemplate = ({ data }) => {
  const post = data.markdownRemark

  return (
    <section>
      <h1>{ post.title }</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
   </section>
  )
}

export default BlogPostTemplate

export const pageQuery = graphql`
  query BlogPostBySlug(
    $id: String!
  ) {
    markdownRemark(id: { eq: $id }) {
      id
      html
      frontmatter {
        title
      }
    }
  }
`

The template has two parts. The GraphQL query where we get all the necessary data for our blog page. And the structure of the React component where we specify how the content is going to be displayed. One detail to note is the use of the React property dangerouslySetInnerHTML to insert the converted HTML content from our Markdown into a DOM element.

This is a simple example but it includes all the elements that make a page template. If your site has different page types that require different templates, additional template files must be created.

Creating pages dynamically

So far we have our Markdown content and the templates that will be used to display the different types of content pages, but Gatsby still doesn't know that we want to create a page for each of the files, nor does it know the path that pages should have or the template to use with each one. This is done through our application's gatsby-node.js file.

If it doesn't already exist in your application, create a gatsby-node.js file in the root of the site. Inside this file we will use a couple of APIs to tell Gatsby how to dynamically create our pages during the site build process.

Using the createPage API we select the nodes for files of a specific type using GraphQL. A regular expression is used to filter and choose only the files that are in a particular directory.

const path = require(`path`)

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

  // Template for blog pages
  const blogPost = path.resolve(`./src/templates/blog-post.js`)


  // Get all the Markdown nodes that posts that are on the blog folder
  const result = await graphql(
    `
      {
        blogPosts: allMarkdownRemark(
          filter: {fileAbsolutePath: {regex: "/\/blog\//"}}
        ) {
          nodes {
            id
            frontmatter {
              path
            }
          }
        }
    `
  )

Then we define the template that we are going to use for this type of page. If we are going to have many types of pages, each with a different template, we can define several queries and templates in this same place.

Finally, we have to generate the pages for each of the results returned by the GraphQL query. For each page we define the path, in this case using a Frontmatter field that has the path that the content had on the original site, and the file that contains the React component that we will use as a template.

  const posts = result.data.blogPosts.nodes

  if (posts.length > 0) {
    posts.forEach((post, index) => {

      createPage({
        path: post.frontmatter.path,
        component: blogPost,
        context: {
          id: post.id,
        },
      })
    })
  }

With this, during the Gatsby site build process a page will be created for each Markdown file within the ./content/blog directory using the template we created especially for this type of content. We can also create additional templates for other types of content from Markdown files stored in other directories as needed.

I hope this has been helpful.

Related
articles

Exporting content from Drupal 7 to Markdown

read moreright arrow

Web Performance Basics—Also What Makes Gatsby Sites Fast?

read moreright arrow