How to create a fast and beautiful blog with Gatsby
The hottest thing right now is to use a static site generator tool to create your blog. This way you only serve pure HTML/CSS/Javascript without any server. This makes the site blazing fast.
My favorite tool for this is Gatsby. It has tons of optimizations out-of-the-box, and it has a very active community.
For devs like you and me, it makes lots of sense to create your blog with a static site generator because you have complete control of your blog. Gatsby is built on React which means you can code React components when you want to extend your blog.
This article will take you through creating a complete blog in Gatsby. The blog you’ll create is going to be very similar to the blog you are reading on right now (blog.jakoblind.no) because I’m using almost the exact same setup. It will not just be a copy-paste tutorial, but I will also teach you the very basics of how Gatsby is built up. How does that GraphQl thing work? How does the plugin system work? Etc. But we’ll start from the beginning creating your blog from scratch.
In this post, you’ll learn the following
- Create your new fresh Gatsby blog
- Let’s change the bio
- How to edit a blog post
- How Gatsby works with data
- Gatsby uses transformers to understand markdown
- Adding tags
- Separating pages and blog posts all written in markdown
- Add a menu
- Changing fonts
- Syntax highlighting
- Google analytics support
- Production build
- A note on caching
- Next steps
Lots of the code in this guide has been heavily inspired (stolen) from the Gatsby docs.
Create your new fresh Gatsby blog
Let’s jump straight into creating your new blog with Gatsby!
Gatsby has templates that give you boilerplate code to get you started quickly. I suggest you use the gatsby-starter-blog starter template. It got some nice stuff such as code for markdown parsing. We need markdown support because we will write our blog posts in markdown. It also has some nice typography using typography.js and code to handle SEO with meta tags.
First, you need to install gatsby globally (you can skip this step if you already have it installed on your machine):
npm install -g gatsby-cli
Now you can create your project using the gatsby-starter-blog
template. Use the following command and replace my-cool-blog
with whatever you want to call your project:
gatsby new my-cool-blog https://github.com/gatsbyjs/gatsby-starter-blog
A new folder my-cool-blog
, or whatever name you specified, is created which contains your new cool blog. Cd into the folder and type ls
to get an overview of what’s there.
jlind@localhost ~/d/my-cool-blog> ls
content/ gatsby-browser.js gatsby-config.js gatsby-node.js LICENSE node_modules/ package.json package-lock.json README.md src/ static/ yarn.lock
A complete project has been generated for you with config files, source code files, etc. To run your new blog on localhost run this command:
npm start
Then go to http://localhost:8000/ and check out your new blog.
As you can see there are some sample blog posts that you can click to get a feel how things look. There’s also a header with Kyle Mathews bio and his profile pic. Let’s change that because this is no longer his blog, but yours :)
Let’s change the bio
Open up the the gatsby-config.js
in your favorite text editor. At the top of the file, there is a key called siteMetadata
. This is the place where you can edit basic information about you such as author
and description
. It will be picked up automatically everywhere where this info is referenced, such as the first page. On my blog I changed it to this:
siteMetadata: {
title: `blog.jakoblind.no`,
author: `Jakob Lind`,
description: `A blog about React, Redux, Webpack and JavaScript`,
siteUrl: `https://blog.jakoblind.no/`,
social: {
twitter: `karljakoblind`,
},
},
This code will pop up on the first page.
Leave the other stuff unchanged for now. We’ll come back to it later.
How to edit a blog post
When creating a blog with the gatsby-starter-blog
template like we did, you get some code generated for you. It has created a content
directory on the root of your project. In that directory, you have blog
folder where all your blog posts are, and an asset
folder where the images and other attachments to blog posts are. This is a pretty good setup I think so we’ll keep it. Go ahead and open a post in the blog post folder, for example, content/blog/hello-world/index.md
:
---
title: Hello World
date: "2015-05-01T22:12:03.284Z"
---
This is my first post on my new fake blog! How exciting!
I'm sure I'll write a lot more interesting things in the future.
Oh, and here's a great quote from this Wikipedia on
[salted duck eggs](https://en.wikipedia.org/wiki/Salted_duck_egg).
> A salted duck egg is a Chinese preserved food product made by soaking duck
> eggs in brine, or packing each egg in damp, salted charcoal. In Asian
> supermarkets, these eggs are sometimes sold covered in a thick layer of salted
> charcoal paste. The eggs may also be sold with the salted paste removed,
> wrapped in plastic, and vacuum packed. From the salt curing process, the
> salted duck eggs have a briny aroma, a gelatin-like egg white and a
> firm-textured, round yolk that is bright orange-red in color.
If you’re not familiar with markdown, it’s a plain text formatting syntax.
The top of the markdown posts is metadata. In this case, it contains the title and the date. Gatsby will use this information to show the title in a h1
tag and it will show the title in the <title>
section. It’ll also use the date to show it and to be able to sort the posts in the post list page to have the latest at the top.
The rest of the markdown file is the actual content of the blog. There is a special syntax for writing, links, images, italic, etc. To learn markdown, mastering markdown by Github is a good resource.
Go ahead and change some of the text of the blog post and see how your changes are reflected in the running app.
The blog posts are in folders. And the folders has an index.md
file and some assets.
.
├── hello-world
│ ├── index.md
│ └── salty_egg.jpg
├── hi-folks
│ └── index.md
└── my-second-post
└── index.md
I prefer to structure my posts in markdown files in the root of the folder. And then have the images and other assets in the content/assets
folder. Most of the images are being reused between posts so I prefer to have them all gathered in one place.
.
├── 2-quick-javascript-tips.markdown
├── 3-steps-to-check-if-isomorphicuniversalssr-is-worth-it.markdown
├── 3-ways-to-reduce-webpack-bundle-size.markdown
├── 4-libraries-to-use-in-your-react-app.markdown
├── 4-questions-to-ask-yourself-when-you-get-stuck-with-react.markdown
├── ajax-componentdidmount-vs-componentwillmount.markdown
This works out of the box, you can just create a new post in the content/blog/
folder and it will be picked up automatically by Gatsby.
How Gatsby works with data
When you changed the bio earlier, you changed the gatsby-config.js
and the code was automagically updated in the posts. But how does that magic work?
You can think of Gatsby having one large database. And it fills up this database with data sources that you define in gatsby-config.js
. In your project, you have two data sources: siteMetadata
as we just saw, and gatsby-source-filesystem
that is used for reading markdown blog post from your filesystem. This is defined in gatsby-config.js
So how does Gatsby access the data from the database? The answer is GraphQL. Open up src/pages/index.js
and scroll to the bottom of the file. There you can see how the index page fetches data from the database with a GraphQL query:
export const pageQuery = graphql`
query {
site {
siteMetadata {
title
}
}
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
edges {
node {
excerpt
fields {
slug
}
frontmatter {
date(formatString: "MMMM DD, YYYY")
title
description
}
}
}
}
}
`
This is an illustration of how the data sources propagate the database, and the GraphQL queries fetch data from the database:
If you don’t know GraphQL you don’t have to learn all the in and outs of it, because in Gatsby you will only do queries with it. But it can be nice to know the basics. You can learn more about GraphQL on their official website.
You can access the GraphQL dev interface on http://localhost:8000/___graphql (assuming you are using port 8000
). It’s very useful to experience with new queries here because you get an instant response, and you can also explore the format of your data here.
Gatsby uses transformers to understand markdown
Did you look in gatsby-config.js
? Then you noticed there are much more things defined in there than our two sources. Another important type of plugin is transformer. In your config, there are two transformers defined: gatsby-transformer-remark
and gatsby-transformer-sharp
It’s the gatsby-transformer-remark
that parses your markdown files after gatsby-source-filesystem
has read it. This plugin puts an HTML version of the markdown files in the database that you can access with GraphQL.
As you notice our blog lacks some things. It doesn’t have tags, and we don’t have support for pages like an /about
page and other static stuff. Let’s create all that stuff now!
Adding tags
Tags are useful for your readers. If they find a good blog post on a topic, for example, Redux, then they are eager to read more posts on the same topic. If your blog supports tags, then they can click your Redux tag that they find on the bottom of your posts:
…and when they do click, they get a list of all posts that covers that topic:
So let’s implement that for our blog now! We’ll start by defining the tags in our markdown files.
As we saw earlier each post has some metadata at the top of the post:
---
title: How to code Redux and Gatsby
date: "2015-05-01T22:12:03.284Z"
---
content
The text inside ---
are metadata. The metadata is a perfect place to add our tags. We’ll support a list of tags, like this:
---
title: How to code Redux and Gatsby
date: "2015-05-01T22:12:03.284Z"
tags:
- redux
- gatsby
---
content
You might need to restart the server for it to pick up these changes properly.
Now, let’s show these tags at the bottom of each blog post. Open up the file src/templates/blog-post.js
in your text editor. Each blog posts are rendered with this template. As you can see it’s a React component and at the bottom, there is a Graphql query.
This query fetches the content, such as body, title, and date of the blog post. The content is injected into the React component as props.
As you learned earlier all data, such as blog posts, are inside the GraphQL database. When you added the tags to the metadata, Gatsby automatically put it in the database. Now you can fetch these tags in the GraphQL query so that the tags for each post is injected into the React component. Add tags
to the GraphQL query in src/templates/blog-post.js
file like this (tags
is highlighted in blue):
export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) {
site {
siteMetadata {
title
author
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
excerpt(pruneLength: 160)
html
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
description
tags }
}
}
`
All metadata from each post are put into the frontmatter
key in the GraphQL database. That’s why we put the tags
in that query. Now you should have tags
with all the tags in the React component. Let’s display them. Go to the React component in the file src/templates/blog-post.js
and add the tags
to a variable:
const tags = post.frontmatter.tags || []
Add that line somewhere in the render
function, but before the return
statement:
class BlogPostTemplate extends React.Component {
render() {
// <--- add it here
return (
If a post doesn’t have any tags we want it to be an empty list so that we can run .map
on it without getting an error. That’s why we add the || []
at the end.
Now let’s add the JSX that will show all tabs at the bottom of each post. We’ll add it in the same file (src/templates/blog-post.js
) right after the <hr>
tag in the JSX that is returned. This is the JSX you’ll add:
<div>
tags:
<ul
style={{
display: `flex`,
flexWrap: `wrap`,
justifyContent: `space-around`,
listStyle: `none`,
}}
>
{tags.map((t) => (
<li key={kebabCase(t)}>
<Link to={`/tags/${kebabCase(t)}`}>{t}</Link>
</li>
))}
</ul>
</div>
We use the lodash.kebabcase to convert tags like hello world
to hello-world
. To use it first install it:
npm i --save lodash.kebabcase
Then import it at the top of the page:
import kebabCase from "lodash.kebabcase"
Now if you open a blog post in your browser, you should see some tags at the bottom of the post. Be sure to pick a post that you actually added some tags to in the markdown header.
The tags are clickable, so if you click the tag redux
it has a link to /tags/redux
. But that page doesn’t exist yet. Let’s create it!
To create a completely new kind of page we need to do two things:
- Add some code in the
gatsby-node.js
file to create pages - Create a template file that we’ll use from
gatsby-node.js
code.
Open up gatsby-node.js
file in your editor. In here you can see that exports.createPages
function. This function creates pages for each individual blog post. It does this by fetching the blog posts with a GraphQL query and then for each post it calls createPage
passing in a template ./src/templates/blog-post.js
the path
to the page and some context variables.
What we are going to do now is to fetch all tags from all blog posts. First, add tags
to the GraphQL query.
{
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
tags // highlight-line
}
}
}
}
}
Next, we’ll write the code that fetches all tags from all blogposts. Put this code in the then
block:
let tags = []
// Iterate through each post, putting all found tags into `tags`
posts.forEach((edge) => {
if (edge.node.frontmatter.tags) {
tags = tags.concat(edge.node.frontmatter.tags)
}
})
// Eliminate duplicate tags
tags = uniq(tags)
The uniq
function removes duplicates, so that if you have redux
tag in three posts, it will only show up once in the list. I have implemented uniq
like this
var uniq = (arrArg) => {
return arrArg.filter((elem, pos, arr) => {
return arr.indexOf(elem) == pos
})
}
The next thing we are going to do is to create a page for each tag in our list.
const tagTemplate = path.resolve("src/templates/tags.js")
// Make tag pages
tags.forEach((tag) => {
createPage({
path: `/tags/${kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag,
},
})
})
We also need to import kebab-case
at the top of the file. This time with the require
syntax:
const kebabCase = require("lodash.kebabcase")
For each call to createPage
we create the URL /tags/THETAG/
and we pass in the template tagTemplate
which is the file src/templates/tags.js
. This is a file that we are going to create now.
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"
const Tags = ({ pageContext, data, location }) => {
const { tag } = pageContext
const { edges, totalCount } = data.allMarkdownRemark
const siteTitle = data.site.siteMetadata.title
const tagHeader = `${totalCount} post${
totalCount === 1 ? "" : "s"
} tagged with "${tag}"`
return (
<Layout location={location} title={siteTitle}>
<SEO title={tagHeader} />
<h1>{tagHeader}</h1>
<ul>
{edges.map(({ node }) => {
const { slug } = node.fields
const { title } = node.frontmatter
return (
<li key={slug}>
<Link to={slug}>{title}</Link>
</li>
)
})}
</ul>
<Link to="/tags">All tags</Link>
</Layout>
)
}
export default Tags
export const pageQuery = graphql`
query ($tag: String) {
site {
siteMetadata {
title
}
}
allMarkdownRemark(
limit: 2000
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { tags: { in: [$tag] } } }
) {
totalCount
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`
src/templates/tags.js
(the code is mostly copy/pasted from the docs page)Interesting things to note here is that we filter out only the relevant posts with the filter
keyword in the GraphQL query.
filter: { frontmatter: { tags: { in: [$tag] } } }
Then in the React component we just list all the posts we find that has that tag.
Now there is only one thing left, and that is to create the /tags
page which will list all tags aggregated from all blog posts.
To do this we’ll create a page. Create the file src/pages/tags.js
:
import React from "react"
import Layout from "../components/layout"
import { Link, graphql } from "gatsby"
import SEO from "../components/seo"
var kebabCase = require("lodash.kebabcase")
const TagsPage = ({
data: {
allMarkdownRemark: { group },
site: {
siteMetadata: { title },
},
},
location,
}) => (
<Layout location={location} title={title}>
<SEO
title="all tags"
keywords={[`blog`, `gatsby`, `javascript`, `react`]}
/>
<div>
<h1>Tags</h1>
<ul>
{group.map((tag) => (
<li key={tag.fieldValue}>
<Link to={`/tags/${kebabCase(tag.fieldValue)}/`}>
{tag.fieldValue} ({tag.totalCount})
</Link>
</li>
))}
</ul>
</div>
</Layout>
)
export default TagsPage
export const pageQuery = graphql`
query {
site {
siteMetadata {
title
}
}
allMarkdownRemark(limit: 2000) {
group(field: frontmatter___tags) {
fieldValue
totalCount
}
}
}
`
We fetch all posts, group them by the tags they have and fetch the tags. Then list them together with the total number of posts they are in. They also link to the tags “details page”.
That was quite a lot of work, but now you are done with your tags system!
Separating pages and blog posts all written in markdown
When you have a blog, you want to create pages such as /about
page, or maybe a sales page, etc, and other types of pages that are not really blog posts, but static pages.
You can, of course, create your own pages by creating React components and put them in the /src/pages/
directory. But there too much boilerplate to write React code and GraphQL queries if all you want is to get some text out there quickly. Then markdown is a better option. So let’s implement the possibility to create markdown pages.
You are going to reuse some of the skills we acquired when creating tags page. It’s a similar workflow.
You are going to put the markdown files in the folder /content/pages/
. Let’s make Gatsby read markdown files from this folder. We’ll do that by adding the following in the gatsby-config.js
file:
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content/pages`,
name: `assets`,
},
},
Now Gatsby will read markdown files from this folder and put it in the GraphQL database. The problem is that it will make no difference between blog posts and pages. All your pages will show up in the blog list on the index page. And all pages will have the tags, next/previous posts, etc at the bottom.
To fix this we need to define different templates for blog posts and pages. The way we are going to do that is to define this information in the metadata. We’ll add a new key called templateKey
. The contents of this key will describe which template we will use.
Create a new markdown file in the content/pages/
directory where you define the templateKey
header as page
, like this:
---
title: About Jakob
date: 2019-05-15
templateKey: page
---
Jakob is a programmer!
templateKey
header with the value blog-post
Now we need to use the template that we define in the markdown file. Open up gatsby-node.js
in your editor. Fetch the templateKey
in the GraphQL query
{
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
tags
templateKey // highlight-line
}
}
}
}
}
Next, we’ll use the templateKey
when creating the page with the createPage
call:
createPage({
path: post.node.fields.slug,
component: path.resolve(
`./src/templates/${post.node.frontmatter.templateKey}.js` ), context: {
slug: post.node.fields.slug,
previous,
next,
},
})
The highlighted lines are the lines that we changed in the file.
Now we must create the page template file. Create a new file src/templates/page.js
with the following contents:
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"
class PageTemplate extends React.Component {
render() {
const post = this.props.data.markdownRemark
const siteTitle = this.props.data.site.siteMetadata.title
return (
<Layout location={this.props.location} title={siteTitle}>
<SEO
title={post.frontmatter.title}
description={post.frontmatter.description || post.excerpt}
/>
<h1>{post.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</Layout>
)
}
}
export default PageTemplate
export const pageQuery = graphql`
query PageBySlug($slug: String!) {
site {
siteMetadata {
title
author
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
excerpt(pruneLength: 160)
html
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
}
}
}
`
Now we use different templates for pages and blog-posts! But if you check the index page, you’ll see that we still see the pages in the listing of blog posts. Let’s filter out the blog posts in the GraphQL query. Open src/pages/index.js
add the following filter:
allMarkdownRemark(
filter: { frontmatter: { templateKey: { eq: "blog-post" } } } sort: { fields: [frontmatter___date], order: DESC }
)
Now there are no longer any pages in the blog posts listing!
Many times you create a page you want to link to it from the menu. And we don’t even have a menu yet. Lets create a menu and link pages from it.
Add a menu
The “correct” way to create a menu in Gatsby is to define the data as a data source in the gatsby-config.js
file, either in the siteMetadata
, or any other data source you are using, such as the filesystem or a CMS data source.
But rules are meant to be broken. When you create a blog that is only meant for you and not a large corporate blog with many different editors that must be able to edit the menu in a CMS, then hard-coding the menu is good enough. And it’s much quicker to implement.
Go to src/components/layout.js
. This is the file for everything that is common across all your pages in your app.
Create a header Menu component at the top of the file:
const Header = () => {
return (
<nav>
<ol>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ol>
</nav>
)
}
And then in the Layout
component that is already defined, use the Header
component in the render function:
<Header />
Now the data is there, all we need now is to add some styling. There is no global css file defined out-of-the-box so let’s create it. Create the file src/components/layout.css
and add the following:
nav ol {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
list-style: none;
color: black;
margin-left: 1.58rem;
}
You also most import it at the top of layout.js
file:
import "./layout.css"
We now have a menu! And if you previously added the page /about
, then you will be able to click the About
link in the menu to see the about page. Sweet!
Changing fonts
The most important design element in a blog is the text. That’s why it’s so important to have good styling of the text to make the blog look beautiful.
Our blog uses typography.js which is a nice library that does a bunch of magic when it comes to fonts. From the home page:
Typography is a complex system of interrelated styles. 100s of style declarations on dozens of elements must be in harmonious order. Trying one design change can mean making dozens of tedious recalculations and CSS value changes. Creating new Typography themes with CSS feels hard.
Typography.js has a wide selection of themes. Go to the home page of typography.js and select a theme at the top right. When you select a theme you’ll get a live preview instantly. Find a theme you like and remember the name. I personally like the lincoln
theme. I’ll use it as an example for the rest of this chapter.
In Gatsby, all themes live as individual NPM modules. Let’s install our NPM theme module:
npm install --save typography-theme-lincoln
Now let’s use it. Go the file src/utils/typography.js
and import the theme at the top:
import Lincoln from "typography-theme-lincoln"
And then change this line
const typography = new Typography(Wordpress2016)
With this
const typography = new Typography(Lincoln)
Also, remove all references to the Wordpress2016
variable which was the default theme.
If you need to do some tweaks to the text styles you can do it with the overrideThemStyles
function on the theme object. I have the following overrides:
Lincoln.overrideThemeStyles = () => {
return {
"nav a": {
color: "black",
textDecoration: "none",
backgroundImage: "none",
textTransform: "uppercase",
// fontFamily: "Montserrat, sans-serif",
},
}
}
Syntax highlighting
prism.js is an awesome tool that we’ll use for syntax highlighting. It’s already installed and configured in your project. Try adding some code to one of your blog posts and wrapp the code in ```.
When you’ll do, the code will look like this in your blog post:
The code is using another font than the rest of the text but it’s still not yet syntax highlighted. Let’s fix this. The way to make it syntax highlighted is to add a theme. You can go to the prism.js home page and see what themes are available. I liked the tomorrow night theme. To use this, add this line at the top of your blog-post.js
file:
require("prismjs/themes/prism-tomorrow.css")
And your code will be highlighted!
Google analytics support
Everything for Google Analytics is already prepared for you. Open gatsby-config.js
. Look for the following code and add your GA tracking id:
{
resolve: `gatsby-plugin-google-analytics`,
options: {
//trackingId: `ADD YOUR TRACKING ID HERE`,
},
},
Production build
Now you’re ready to create a production build. Run the following command
gatsby build
It will create a build and put all files in public
directory. To run your production build locally run the following:
gatsby serve
This will start a web server on port 9000
and serve your static files from there. Go to http://localhost:9000/ to check it out!
A note on caching
Gatsby uses caching heavily to make your site as quick as possible. The downside to this is that your app sometimes not reflect any changes you make. If you have a weird bug that seems impossible to do anything about try removing all cache. You’ll do it by removing the .cache
file.
rm -rf .cache
Next steps
Now that you have an awesome blog, you want to share it with the rest of the world. To be able to do that you must deploy somewhere. There are a bunch of different options for this. I use Netlify and it’s awesome.