Building a blog with Gatsby

I needed my own blog to be customizable, easy to update and easy to maintain, and I did not want to spend too much time on building a complete blog from scratch myself, so I chose to start from an existing base 🀞

There are a couple of great blog CMS like Ghost, but they are often limited to building basic blogs, as there was no simple and scalable way of having custom pages to talk about my projects and trips. I decided to build my website in Javascript with the help of a blog framework, which sounded like a good compromise between customization and reduced development time.

This very website is built in React with Gatsby. Gatsby takes your React code and transpiles it into static assets: HTML, CSS, some Javascript and other static files like images. Also:

  • It loads super fast
  • You don’t need to deal with an API or a database
  • It can be deployed for free easily

Many websites use this framework, such as React website, Airbnb engineering blog, Figma or Sendgrid docs.

gatsby overview
Gatsby structure: serving statically built React app

The blog consists of Gatsby config files, React pages, React components used in those pages and Markdown articles that will be displayed in your blog.

To understand this post you need to understand how Gatsby and React work first. Follow Gatsby’s tutorials, they are great πŸ› 

Technology

I used Gatsby’s blog starter as a base. It’s a Gatsby project containing everything you need to start a blog:

  • A React component listing blog posts
  • A React component displaying a blog post and taking care of SEO
  • Basic styling
  • Markdown .md files in a /content folder to store blog posts

starter blog
Basic template of Gatsby’s blog starter

With this you are already settled. You have a blog! You can already edit articles, build the blog with yarn build and upload the transpiled HTML and CSS files on some server πŸŽ‰

In my case I needed more customization so I also:

  • Created more pages and components
  • Set up a more complex /content folder that would store more than blog posts (see Contents chapter below)
  • Used styled-components for styling. Don’t forget to add gatsby-plugin-styled-components to transpile styling in a CSS file correctly on build
  • Installed many Gatsby plugins. There are 600+ plugins available to do a lot of useful things, browse them and you will get ideas πŸ’‘ The blog starter already uses a lot of plugins
  • Used Now for hosting (see Hosting chapter below)

Contents

Once you understand how Gatsby GraphQL logic works and how Gatsby’s blog starter structures its blog posts, you can add another level in the content folder to categorize your content.

In the case of this blog I have 3 types of contents:

  • Blog posts
  • Projects
  • Trips

This is how I organized my content folder:

.
β”œβ”€β”€ content
|   β”œβ”€β”€ posts
|   |   β”œβ”€β”€ advices-to-ship-a-product-for-real
|   |   |   β”œβ”€β”€ feature.png
|   |   |   └── index.md
|   |   └── building-a-blog-with-gatsby
|   |       β”œβ”€β”€ feature.png
|   |       └── index.md
|   β”œβ”€β”€ projects
|   |   β”œβ”€β”€ drystack
|   |   |   β”œβ”€β”€ index.md
|   |   |   └── logo.png
|   |   └── overactive
|   |       β”œβ”€β”€ index.md
|   |       └── logo.png
|   └── trips
|       β”œβ”€β”€ 2014-09-gothenburg
|       |   └── index.md
|       └── 2016-11-san-francisco
|           └── index.md
β”œβ”€β”€ src
β”œβ”€β”€ static
└── etc.

The file system plugin (gatsby-source-filesystem) will have a look at the /content folder with a configuration like this:

// gatsby-config.js

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

The markdown plugin (gatsby-transformer-remark) will read .md files in those folders (tagged by names posts, projects and trips) and interpret them. Those files have a common shape:

  • metadata at the top (between --- marks)
  • the content below the metadata

Here is how blog posts .md files look like:

---
title: How to find product ideas
description: My methods to generate valuable web businesses ideas
date: '2018-10-23'
image: ./feature.jpeg
draft: true
---

Many developers want to build their own projects [...]

Projects look like:

---
title: Drystack
description: The best tech stacks to build your next product
date: '2018-07-20'
url: https://drystack.io
logo: ./logo.png
color: '#F1FFDE'
---

Trips needed more complex meta data which can be organized as nested properties and can be later used as javascript objects, like this:

---
city: San Francisco
coordinates:
  lat: 37.7749
  lng: -122.4194
country: USA
countryCode: US
dates:
  from: '2016-11-01'
  to: '2017-08-30'
---

I worked for 1 year in a startup in SOMA [...]

Now you want to use those files in your blog. When querrying any markdown contents with GraphQL in Gatsby you use allMarkdownRemark which returns all the .md files the script found. Even if we specified 3 folders (posts, projects and trips) all those files will be returned together, without knowing which file is a post or a project 😱 Let’s start by identifying each file retrieved by allMarkdownRemark as a blog post, a project or a trip.

In gatsby-node.js we update the onCreateNode function to add a new type field to our nodes:

// gatsby-node.js

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  // ...

  // in the "Starter blog" this part already exists and adds a `slug` field to the node, here we add another field `type`
  if (node.internal.type === `MarkdownRemark`) {
    // `sourceInstanceName` here is the name of the folder the .md file is in
    const type = getNode(node.parent).sourceInstanceName
    // set a `type` field to the node which will be "posts", "projects" or "trips"
    createNodeField({ name: 'type', node, value: type })
  }
}

We now have a fields.type property in Markdown files data used by GraphQL. We can access them and therefore filter queries with them. Let’s query all projects, we use the filter to get only the nodes with a "projects" type:

// pages/projects.js

import React from 'react'
import { graphql } from 'gatsby'

class Projects extends React.Component {
  render() {
    const { data } = this.props
    const projects = data.projects.edges

    return projets.map(({ node }, index) => {
      const { title } = node.frontmatter
      return <div key={index}>{title}</div>
    })
}

export default Projects

export const pageQuery = graphql`
  query {
    projects: allMarkdownRemark(
      filter: { fields: { type: { eq: "projects" } } }
      sort: { fields: [frontmatter___date], order: DESC }
    ) {
      edges {
        node {
          frontmatter {
            title
          }
        }
      }
    }
  }
`

The Projects page will receive only nodes of type project. Awesome πŸ™Œ

World map

On my trips page there is a map like this one:

map

It displays all the places of the business trips I did. They have a coordinates metadata locating them geographically. Each trip is a .md file in /content/trips. To retrieve only the trips, I used the same method as above with projects nodes but this time filtering by trips type.

I used React Leaflet to display the map, using the Wikimedia open street map theme and placed a blue CircleMarker for each city I visited πŸ“

Automated colors

This is more of a nice to have but I wanted to show how you can get a more complex behavior using Javascript and .md files content πŸ˜„

Each project has a logo and a theme color. This allows me to define only one color I like for a project, then use it with a color manipulation library to generate related colors. I could automatically get a darker color for the project title, or a lighter color for a background 🎨 Since colors could be too strong and look weird behind the project logo, I made a function that would lighten and saturate this color so it looks good when coupled with the logo.

The function would generate a color like this:

from
to
or
from
to
etc.
// utils/index.js

import chroma from 'chroma-js'

// fix the saturation and the lightness of a color
// basically we just keep its original hue
export const toBackgroundColor = color =>
  chroma(color)
    .set('hsl.s', 0.8)
    .set('hsl.l', 0.96)
    .css()

I built a React component ProjectImage that displays a project logo in a square of the project’s color (transformed to look good as a background).

// components/ProjectImage.js

import React from 'react'
import styled from 'styled-components'

import { toBackgroundColor } from '../utils'

const Background = styled.div`
  width: 90px;
  height: 90px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: ${props => toBackgroundColor(props.color)};
  border-radius: 4px;

  img {
    width: 46px;
    height: 46px;
  }
`

const ProjectImage = ({ color, logo }) => (
  <Background color={color}>
    <img src={logo} />
  </Background>
)

It results in a light background color behind the logo:

project drystack

Pagination

I made a pagination on the blog posts page, limited to 6 posts per page. It looks like this:

pagination

I simply used a very similar approach than the one in this Github repository: Gatsby paginated blog example. It consists of transforming the pages/posts.js into a templates/blog-posts.js used in gatsby-node.js to generate each posts page. The Github code is pretty explicit but you need a good understanding of Gatsby to copy it.

SEO

I updated the original SEO component from Gatsby’s blog starter to make it more robust and better support Twitter sharing metadata. Here it is:

// components/SEO.js

// use `static/og-image.png` by default
const metaImage = `${data.site.siteMetadata.siteUrl}${image ||
  withPrefix('/og-image.png')}`

// use `static/og-logo.png` by default
const metaLogo = `${data.site.siteMetadata.siteUrl}${withPrefix(
  '/og-logo.png',
)}`

// add blog name to all titles
const metaTitle = title
  ? `${title} | ${data.site.siteMetadata.title}`
  : `${data.site.siteMetadata.title} | ${data.site.siteMetadata.activity}`

// use config description as default
const metaDescription = description || data.site.siteMetadata.description

return (
  <Helmet
    title={metaTitle}
    meta={[
      {
        name: 'description',
        content: metaDescription,
      },
      {
        property: 'og:title',
        content: metaTitle,
      },
      {
        property: 'og:description',
        content: metaDescription,
      },
      {
        property: 'og:type',
        content: 'website',
      },
      {
        property: 'og:logo',
        content: metaLogo,
      },
      {
        property: 'og:image',
        content: metaImage,
      },
      {
        name: 'twitter:card',
        content: 'summary_large_image',
      },
      {
        name: 'twitter:creator',
        content: `@${data.site.siteMetadata.social.twitter}`,
      },
      {
        name: 'twitter:title',
        content: metaTitle,
      },
      {
        name: 'twitter:description',
        content: metaDescription,
      },
      {
        name: 'twitter:image',
        content: metaImage,
      },
    ]}
  />
)

Hosting

There are some very good free offers for static website hosting, essentially Netlify and Now. Netlify is the simplest one, Now is a bit more advanced (but still simple to use don’t worry)

For this blog I used Now, but really Netlify would have worked the same.

Now has an incredible CLI. Just type now and it sends your code to Now before being built, sent to their CDN and deployed. Here is how it looks like:

now deploy

It creates an url for each of your deployments that can be accessed instantly.

now deployed

I configured my custom domain name jean-elie.com (bought with Namecheap) to send the visitor to Now DNS, which serves the blog for free, with free SSL (thanks to Let’s Encrypt). Here is there tutorial for using custom domain. You can learn more about deploying Gatsby to Now on Gatsby documentation.

Conclusion

Gatsby works great, until now I could do everything I wanted with it, hosting is free and cache is so well managed with Now that server bandwidth usage stays very small 😍 Let me know on Twitter if you have questions or improvements ideas.


Share this post