Building this site with Gatsby

June 14, 2020

Building this site with Gatsby

Why Gatsby?

So, if you read my hello-world post you will know that the decision to go to Gatsby was for a few reasons:

  • I didn't want a database, so I had been looking into static site generators for some time, I'd used Jekyll in the past on a client project
  • I wanted to work with something I wasn't immediately familiar with so I could try and learn something new
  • I wanted to get up and running as quickly as possible after the initial learning

In the end it came down to Gatsby and NextJS and I settled on Gatsby purely because I didn't want to run a server. I just wanted something compiled rather than server side generated.

Below is an overview of the Gatsby plugins and pieces I used to build this site.

Build Overview

First off, I followed the tutorials over at https://www.gatsbyjs.org/tutorial/part-zero/ to familiarise myself with Gatsby, this site is pretty much built off of the guides there.

Init via the starter theme

First off, I used the most barebones starter there is - the 'hello world' starter rather than the 'default starter'

# under my project folder this creates `app` subdir with gatsby
gatsby new app https://github.com/gatsbyjs/gatsby-starter-hello-world

Handling Markdown files

For me, I'm was going pure static and wanted to use Markdown files. To do so, I needed two main plugins: gatsby-source-filesystem and gatsby-transformer-remark

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

Configuration for gatsby-source-filesystem is quite straightforward

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

I also wanted to use PrismJS to handle any code being rendered from the MD files, I installed and configured it as follows:

npm install --save gatsby-remark-prismjs prismjs

The configuration I am using for it is below:

{
  resolve: `gatsby-transformer-remark`,
  options: {
    plugins: [
      {
        resolve: `gatsby-remark-prismjs`,
        options: {
          classPrefix: "language-",
          inlineCodeMarker: null,
          aliases: {},
          showLineNumbers: false,
          noInlineHighlight: false,
          languageExtensions: [
            {
              language: "superscript",
              extend: "javascript",
              definition: {
                superscript_types: /(SuperType)/,
              },
              insertBefore: {
                function: {
                  superscript_keywords: /(superif|superelse)/,
                },
              },
            },
          ],
          prompt: {
            user: "root",
            host: "localhost",
            global: false,
          },
          escapeEntities: {},
        },
      },
    ],
  },
},

In gatsby-node.js there is some configuration for both these plugins to parse files at build or develop time so that Gatsby can create pages from the markdown files. In the tutorial this is done by creating a new field slug based off the filename.

Optionally, we can also create the slugs using some front-matter in the markdown file.

Below we can see both in action:

// required for creating slugs
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

// on build/develop when nodes are being created
// only required if needing to add a 'slug' field to the node for use
// in createPages below
exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions

  // if the type id from our MarkdownRemark transformer
  if (node.internal.type === `MarkdownRemark`) {
    // to find out more about the node
    // https://www.gatsbyjs.org/tutorial/part-seven/
    // console.log(node.internal)

    // create the slug var
    const slug = createFilePath({ node, getNode, basePath: `pages` })

    // pass the slug to createNodeField which extends the current node being parsed
    // https://www.gatsbyjs.org/docs/actions/#createNodeField
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

// create pages for nodes built off markdown files and slugs above
// https://www.gatsbyjs.org/docs/node-apis/#createPages
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const result = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              path
            }
            fields {
              slug
            }
          }
        }
      }
    }
  `)

  // iterate through the graphql markdown result edges and create pages
  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      //path: node.fields.slug,
      path: node.frontmatter.path,
      component: path.resolve(`./src/templates/blog-post.js`),
      context: {
        // Data passed to context is available
        // in page queries as GraphQL variables.
        slug: node.fields.slug,
      },
    })
  })
}

A simple template file to use this setup might look something like this:

import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default ({ data }) => {
  const post = data.markdownRemark
  return (
    <Layout>
      <div>
        <h1>{post.frontmatter.title}</h1>
        <p>Date: {post.frontmatter.date}</p>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
      </div>
    </Layout>
  )
}

// graphql to get the markdown data based on the slug
export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        path
        title
      }
    }
  }
`

Preparing for Production

In the tutorials they go over preparing the site for production, for this part I simply used gatsby-plugin-manifest and gatsby-plugin-offline to make the site more of a progressive web app (PWA). I also used react-helmet (gatsby-plugin-react-helmet) to add page metadata easily to the site.

Adding support for the manifest plugin:

npm install --save gatsby-plugin-manifest

Configuring the plugin:

{
  plugins: [
		...
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `GatsbyJS`,
        short_name: `GatsbyJS`,
        start_url: `/`,
        background_color: `#6b37bf`,
        theme_color: `#6b37bf`,
				display: `standalone`,
        icon: `src/images/icon.png`,
      },
    },
  ]
}

Offline support is next

npm install --save gatsby-plugin-offline

And add this plugin to the gatsby-config.js file

{
  plugins: [
		...
    `gatsby-plugin-offline`,
  ]
}

To add page meta data I installed the following:

npm install --save gatsby-plugin-react-helmet react-helmet

Update the gatsby-config.js file meta data and add the plugin

siteMetadata: {
    title: `My website`,
    description: `A website about random stuff...`,
    author: `me`,
  },
	plugins: [
  {
    ...
	  `gatsby-plugin-react-helmet`,
	],
	...
}

You can then create a component to house this data in something like src/components/seo.js

import React from "react"
import PropTypes from "prop-types"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"

function SEO({ description, lang, meta, title }) {
  const { site } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
            description
            author
          }
        }
      }
    `
  )

  const metaDescription = description || site.siteMetadata.description

  return (
    <Helmet
      htmlAttributes={{
        lang,
      }}
      title={title}
      titleTemplate={`%s | ${site.siteMetadata.title}`}
      meta={[
        {
          name: `description`,
          content: metaDescription,
        },
        {
          property: `og:title`,
          content: title,
        },
        {
          property: `og:description`,
          content: metaDescription,
        },
        {
          property: `og:type`,
          content: `website`,
        },
        {
          name: `twitter:card`,
          content: `summary`,
        },
        {
          name: `twitter:creator`,
          content: site.siteMetadata.author,
        },
        {
          name: `twitter:title`,
          content: title,
        },
        {
          name: `twitter:description`,
          content: metaDescription,
        },
      ].concat(meta)}
    />
  )
}

SEO.defaultProps = {
  lang: `en`,
  meta: [],
  description: ``,
}

SEO.propTypes = {
  description: PropTypes.string,
  lang: PropTypes.string,
  meta: PropTypes.arrayOf(PropTypes.object),
  title: PropTypes.string.isRequired,
}

export default SEO

From here, you can use this component in the site (example from blog-post.js below)

import SEO from "../components/seo"

<SEO title={post.frontmatter.title} 
 description={post.frontmatter.description || post.excerpt} />

//graphql for this
export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        path
        title
        description
      }
      excerpt(pruneLength: 55)
    }
  }
`

Summary

We've covered using the hello-world start theme from Gatsby along with a handful of plugins to create a very minimal site without too much guff:

gatsby-source-filesystem
gatsby-transformer-remark
gatsby-remark-prismjs
gatsby-plugin-manifest
gatsby-plugin-offline
gatsby-plugin-react-helmet