Building this site with Gatsby
June 14, 2020
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