I recently found out about GatsbyJS after they announced GatsbyJS V2 Beta. Having a blog and posting about stuff I work on has been on my to-do list for too long, so I decided to build my own blog using GatsbyJS! This first post is a step-by-step guide on how I built this blog using the v2 beta.
GatsbyJS
GatsbyJS is a JavaScript web framework that lets you build fast websites and automatically optimizes them using modern web performance ideas (e.g. PRPL Pattern).
GatsbyJS is really simple, the configuration is handled by the framework and the plugins. All you have to do is:
- Build your website using React components
- Pull your data from any source into your pages using GraphQL
- Build an optimized website as static files ready to be deployed using the Gatsby CLI
You can find Gatsby’s features in detail here, compared to popular alternatives.
Plugins
Plugins are Node.js packages that implement Gatsby APIs. They enable you to easily add features to your Gatsby app such as: setup Sass, adding Typescript, etc.
GatsbyJS has a large set of plugins, take a look at the Plugin Library.
Starters
When creating a new site, you can optionally specify a starter to base your new site on.
There are 3 official starters and many others maintained by the community, you can find the list here.
In this tutorial, we are going to use the gatsby-starter-blog
official blog starter since it is already configured with all the necessary plugins to get your blog started.
Markdown
This blogpost is a Markdown file that has been transformed into a page programmatically. It looks like this:
---
title: "How to create your blog using GatsbyJS (V2 Beta)"
date: "2018-07-21"
tags: ["GatsbyJS", "React", "GraphQL", "Markdown"]
---
I recently found out about GatsbyJS [...]
At the top of the file, we have YAML-formatted key value pairs that are relevant to the blogpost called frontmatter. The rest of the file is the content of the blogpost in Markdown format.
To make the transformation happen :
- We read files into GatsbyJS from the filesystem using the
gatsby-source-filesystem
plugin. - We transform markdown to HTML and frontmatter to data using the
gatsby-transformer-remark
plugin. - We programmatically create pages using Gatsby’s APIs
Installation
Get Gatsby CLI
npm install --global gatsby-cli
Create your blog using the GatsbyJS V2 beta’s official blog starter
gatsby new blog https://github.com/gatsbyjs/gatsby-starter-blog#v2
Navigate to your blog’s folder
cd blog
And start a hot-reloading development environment accessible at localhost:8000
gatsby develop
We now have a simple yet functional blog running!
App structure
Let’s take a quick look at your app’s structure which should look like this:
src
components // Where React components will be
pages // Where pages will be
templates // Where page templates will be
utils // Classic utils folder
gatsby-config.js // Gatsby's config file, plugins list
gatsby-node.js // File where we programmatically create pages
How to create pages
Pages can be created in many ways.
Automatically creating pages
GatsbyJS core automatically turns React components in src/pages
into pages:
pages/
index.js
404.js
… will become …
my-website/
index.html
404.html
Let’s take a look at index.js
to understand how it works.
import React from 'react'
import { Link } from 'gatsby'
import get from 'lodash/get'
import Helmet from 'react-helmet'
import Bio from '../components/Bio'
import Layout from '../components/layout'
import { rhythm } from '../utils/typography'
class BlogIndex extends React.Component {
render() {
const siteTitle = get(this, 'props.data.site.siteMetadata.title')
const posts = get(this, 'props.data.allMarkdownRemark.edges')
return (
<Layout location={this.props.location}>
<Helmet title={siteTitle} />
<Bio />
{posts.map(({ node }) => {
const title = get(node, 'frontmatter.title') || node.fields.slug
return (
<div key={node.fields.slug}>
<h3
style={{
marginBottom: rhythm(1 / 4),
}}
>
<Link style={{ boxShadow: 'none' }} to={node.fields.slug}>
{title}
</Link>
</h3>
<small>{node.frontmatter.date}</small>
<p dangerouslySetInnerHTML={{ __html: node.excerpt }} />
</div>
)
})}
</Layout>
)
}
}
export default BlogIndex
export const pageQuery = graphql`
query IndexQuery {
site {
siteMetadata {
title
}
}
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
edges {
node {
excerpt
fields {
slug
}
frontmatter {
date(formatString: "DD MMMM, YYYY")
title
}
}
}
}
}
`
The index page queries markdown files and produces a list of blog post titles and excerpts. We can split the file in 2 parts:
- A React component rendering a layout, a bio and a list of blogposts using props.
- A GraphQL query, pulling the data page components need.
GraphQL is a query language similar to SQL. You describe the data you want in your component and then that data is given to you.
In Gatsby, the result of the query is automatically inserted into your React component on the data
prop.
In the first part of the query, we’re pulling the siteMetadata
:
`
...
site {
siteMetadata {
title
}
}
...
`
site.siteMetadata
is common data we want to reuse across the app (like title or description) added to the gatsby-config.js
file. It is automatically added to the GraphQL schema:
module.exports = {
siteMetadata: {
title: 'Gatsby Starter Blog',
author: 'Kyle Mathews',
description: 'A starter blog demonstrating what Gatsby can do.',
siteUrl: 'https://gatsbyjs.github.io/gatsby-starter-blog/',
},
plugins: [
...
]
The second part of the query pulls all the markdown files’ (blogposts) title, date, slug and excerpt filtered by date:
`
...
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
edges {
node {
excerpt
fields { # fields created dynamically in gatsby-node.js
slug
}
frontmatter { # markdown file headers
date(formatString: "DD MMMM, YYYY")
title
}
}
}
}
`
The gatsby-transformer-remark
plugin parses each Markdown files into a MarkdownRemark node and also adds additional fields to the MarkdownRemark GraphQL including html, excerpt, etc.
There is an awesome tool you will need when building your GraphQL queries called GraphiQL
. It is a powerful IDE that lets you “play” with GraphQL types and fields. You can press CTRL+SPACE
to use the auto-complete feature.
GraphiQL is available on your development server at http://localhost:8000/___graphql
:
If it’s your first time seeing GraphQL, this tutorial is a good way to start.
Programmatically creating pages
Page templates
To create pages programmatically, the first thing we need is a page template. Since we used the blog starter, we already have a basic page template in the src/templates
folder named blog-post.js
.
import React from 'react'
import Helmet from 'react-helmet'
import { Link } from 'gatsby'
import get from 'lodash/get'
import Bio from '../components/Bio'
import Layout from '../components/layout'
import { rhythm, scale } from '../utils/typography'
class BlogPostTemplate extends React.Component {
render() {
const post = this.props.data.markdownRemark
const siteTitle = get(this.props, 'data.site.siteMetadata.title')
const { previous, next } = this.props.pageContext
return (
<Layout location={this.props.location}>
<Helmet title={`${post.frontmatter.title} | ${siteTitle}`} />
<h1>{post.frontmatter.title}</h1>
<p
style={{
...scale(-1 / 5),
display: 'block',
marginBottom: rhythm(1),
marginTop: rhythm(-1),
}}
>
{post.frontmatter.date}
</p>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
<hr
style={{
marginBottom: rhythm(1),
}}
/>
<Bio />
<ul
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between',
listStyle: 'none',
padding: 0,
}}
>
{previous && (
<li>
<Link to={previous.fields.slug} rel="prev">
← {previous.frontmatter.title}
</Link>
</li>
)}
{next && (
<li>
<Link to={next.fields.slug} rel="next">
{next.frontmatter.title} →
</Link>
</li>
)}
</ul>
</Layout>
)
}
}
export default BlogPostTemplate
export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) {
site {
siteMetadata {
title
author
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
html
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
}
}
}
`
Like the JS files in src/pages
, we have a React component and a GraphQL query pulling the data (HTML, frontmatter, siteMetadata) we need to render our page.
You may have noticed that we create 2 objects on line 14 using this.props.pageContext
:
const { previous, next } = this.props.pageContext
When dynamically creating pages using Gatsby APIs, we can pass additional data to templates via context. (see next block)
Create static pages
GatsbyJS exposes a powerful Node.js API. This API is available in the gatsby-node.js file in the root directory of your project. Each export found in this file will be run by Gatsby.
In this case, we only care about the createPages
and the onCreateNode
APIs:
createPages
API, is called at build time with injected parametersactions
andgraphql
.onCreateNode
API, is called when a new node is created by a plugin with injected parametersnode
,actions
,getNode
.
Let’s take a look at the gatsby-node.js
file:
const _ = require('lodash')
const Promise = require('bluebird')
const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
const blogPost = path.resolve('./src/templates/blog-post.js')
resolve(
graphql(
`
{
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }, limit: 1000) {
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`
).then(result => {
if (result.errors) {
console.log(result.errors)
reject(result.errors)
}
// Create blog posts pages.
const posts = result.data.allMarkdownRemark.edges;
_.each(posts, (post, index) => {
const previous = index === posts.length - 1 ? null : posts[index + 1].node;
const next = index === 0 ? null : posts[index - 1].node;
createPage({
path: post.node.fields.slug,
component: blogPost,
context: { //data passed to the template
slug: post.node.fields.slug,
previous,
next,
},
})
})
})
)
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
To create a page, we first need a slug (or path). We want to use each Markdown file name to create the page slug so pages/hello-world/index.md
or pages/hello-world.md
will become /hello-world/.
The code inside onCreateNode
creates a new field called slug
everytime the MarkdownRemark
plugin creates a node:
- We use the
createFilePath
function imported from thegatsby-source-filesystem
plugin to create the slug. - And then we use the
createNodeField
action to create the additional field.
The code inside createPages
creates a new page for every Markdown files in pages/
:
- We query Markdown files using the
graphql
injected param. - And then we use the
createPage
action to create a page for each of the Markdown files using theblog-post.js
template.
We now know how to create pages, let’s add some style.
Adding Style
When it comes to adding style, you have a lot of options:
- Classic: Less / Sass
- CSS-in-JS: Glamor / Styled Components
- CSS Modules
- Typography.js
In my case, I didn’t want to add too much new stuff to my code and steepen my learning curve so I decided to stick with Sass and spice it up a little bit with Typography.js
Typography.js
Typography.js is a JavaScript library which generates the base CSS for all your elements. It makes it easy to create a global custom and unique design to your project. Typography also has more than 30 ready-to-use themes you can install as Node.js packages. You can play with them here. Using a theme you like as a base and overriding it is a good way to have a nice looking blog without much effort.
That’s exactly what’s done in the Gatsby Blog Starter thanks to the Typography Gatsby Plugin already installed in gatsby-config.js
:
{
resolve: 'gatsby-plugin-typography',
options: {
pathToConfigModule: 'src/utils/typography',
},
}
If you take a look at the the src/utils/typography.js
file, you will see that the Typography
package and the Wordpress2016
Typography theme package are imported and used:
import Typography from 'typography'
import Wordpress2016 from 'typography-theme-wordpress-2016'
Wordpress2016.overrideThemeStyles = () => ({
'a.gatsby-resp-image-link': { // change rule on this selector
boxShadow: 'none',
},
})
delete Wordpress2016.googleFonts // delete theme fonts
const typography = new Typography(Wordpress2016)
// Hot reload typography in development.
if (process.env.NODE_ENV !== 'production') {
typography.injectStyles()
}
export default typography
export const rhythm = typography.rhythm
export const scale = typography.scale
The Wordpress2016
theme style is being overridden (see comments) and given as a parameter to Typography so it is applied globally.
In my case, I used the Github theme and changed the background color of the body and the font color of links and bold text:
import Typography from 'typography'
import githubTheme from 'typography-theme-github'
githubTheme.overrideThemeStyles = () => ({
'body': {
background: "#fbfafc"
},
'a, strong': {
color: "#fb1d1d"
}
})
const typography = new Typography(githubTheme)
// Hot reload typography in development.
if (process.env.NODE_ENV !== 'production') {
typography.injectStyles()
}
export default typography
Setup Sass
To setup Sass, you only need to install the gatsby-plugin-sass
and all the configuration will be handled by Gatsby.
Install the plugin
npm install --save gatsby-plugin-sass
Include the plugin in your gatsby-config.js file
plugins: [
[...],
`gatsby-plugin-sass`
],
Now you can create .scss
files and import them!
Adding simple SEO
The final step is adding simple SEO to your blog using React Helmet and the React Helmet Gatsby plugin. The React Helmet package as well as the Gatsby plugin are already installed since we used the blog starter.
To implement the basic SEO meta tags, I created a simple React component using props to render the tags:
import React from 'react'
import Helmet from 'react-helmet'
class SEO extends React.Component {
render() {
return (
<Helmet>
{/* General tags */}
<title>{this.props.title}</title>
<meta name="description" content={this.props.description} />
<meta name="image" content={this.props.image} />
{/* OpenGraph tags */}
<meta property="og:url" content={this.props.url} />
{this.props.isBlogpost ? <meta property="og:type" content="article" /> : null}
<meta property="og:title" content={this.props.title} />
<meta property="og:description" content={this.props.description} />
<meta property="og:image" content={this.props.image} />
{/* Twitter Card tags */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content={this.props.twitter} />
<meta name="twitter:title" content={this.props.title} />
<meta name="twitter:description" content={this.props.description} />
<meta name="twitter:image" content={this.props.image} />
</Helmet>
)
}
}
export default SEO
And then imported the SEO
component into my pages/templates (src/templates/blog-post.js):
import React from 'react'
import SEO from '../utils/seo'
class BlogPostTemplate extends React.Component {
render() {
const post = this.props.data.markdownRemark
const siteMeta = this.props.data.site.siteMetadata
const indexSeo = {
title: `${post.frontmatter.title} | ${siteMeta.title}`,
description: post.excerpt,
image: siteMeta.image,
url: `${this.props.data.site.siteMetadata.siteUrl}${post.fields.slug}`,
isBlogpost: true,
twitter: siteMeta.twitter
}
return (
<Layout location={this.props.location}>
<SEO { ...indexSeo } />
[...]
</Layout>
)
}
}
The siteMetadata
object inside the gatsby-config.js
is good place to store all common data needed for SEO:
module.exports = {
siteMetadata: {
title: 'Sevket Yalcin',
author: 'Sevket Yalcin',
description: 'Full Stack Web Developer who lives and works in Tokyo. I build stuff on my free time and blog about it here.',
siteUrl: 'https://sevketyalcin.com',
twitter: '@sev_yalcin',
image: 'https://lh3.googleusercontent.com/NUDV52VqjIcOIFLdaP_pOfb2qfJ7TnaR8-ysRw-BX4Gs4LhLTsygGy0Vmb9mWIvFSsYSHj_BnIs',
},
plugins: [...]
}
You can then edit your GraphQL queries to pull those data (src/templates/blog-post.js):
export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) {
site {
siteMetadata {
title
author
title
description
image
siteUrl
twitter
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
html
excerpt
fields {
slug
}
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
}
}
}
`
Conclusion
After running gatsby build
, you will have a simple yet nice little static blog ready to be deployed inside the public/
folder. I highly recommend Netlify as your static hosting platform. With Netlify, you can easily setup continuous integration from your Github repository!
GatsbyJS is really a powerful, magic framework. The GatsbyJS team and community is working every day to make it even easier to use with more and more features. I really enjoyed building this blog with GatsbyJS and will keep improving it. GatsbyJS is my go-to framework for static websites!
Thank you for reading! This is my first blogpost ever, all your comments and suggestions are welcome!