Build a Blog with Gatsby, Part 1
January 17th, 2019
I decided to rebuild my site with Gatsby and Netlify. The first post describes basic setup and getting the blog up functionality and running.
The repo for this project can be found here: https://github.com/thephilgray/thephilgray-gatsby
Scaffold the Project
npm i -g gatsby-cli
gatsby new <project-name>
Setup a Blog
Create the posts
directory in src/posts
and include a markdown file with yaml
frontmatter:
---
title: EPUB Dev Testing and Debugging
tags: EPUB, E-books, Node, JavaScript, Testing, Cypress, CLI, Android, Mobile
slug: epub-dev-testing-and-debugging
date: 2019-01-11
---
### EPUB Dev Testing
For the past five years or so, many of us have become accustomed to developing websites and web apps using powerful build tools that speed up our workflow and allow us to focus on really interesting features and design decisions without needing to sacrifice performance and usability.
Assuming gatsby-source-filesystem
is installed, add an object to the plugins
array in gatsby-config.js
for each directory of files you want to expose as a collection.
This object must resolve
to the plugin gatsby-source-filesystem
and should include an options
object which includes a name
and path
for the directory of files it reads and exposes to the graphql api.
There can be multiple instances of this plugin. For instance, in the example below derrived from the current default template which already included gatsby-source-filesystem
configured to expose the images
directory, I've added a new instance which also exposes the posts
directory.
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
]
Install gatsby-transformer-remark
npm i --save gatsby-transformer-remark
Add gatsby-transformer-remark
to the plugins
array in gatsby-config.js
plugins: [
// ....other plugins
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
`gatsby-transformer-remark`,
]
Restart gatsby develop
so that the posts
directory is read. You do not need to restart for new values and files, but you do for new config properties and new yaml fields to register.
Query the markdown data at http://localhost:8000/___graphql
query BLOG_POST_LISTING {
allMarkdownRemark(
limit: 5
sort: { order: DESC, fields: [frontmatter___date] }
) {
edges {
node {
frontmatter {
title
slug
date(formatString: "MMMM DD, YYYY")
tags
}
excerpt
id
}
}
}
}
Explore the docs for more fields, filters, and sorting.
There are two ways to access the graphql
data from inside a component:
- using the
StaticQuery
component - using a page query
Since the StaticQuery
component can be used in any component and because our query does not rely on variables (via pageContext
), it's a good choice for the BlogPostListing
component.
Pass a function to the StaticQuery
component's render
prop from which we can destructure the allMarkdownRemark
object from data
and map over it.
import React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import BlogPostListingItem from './BlogPostListingItem'
const BLOG_POST_LISTING = graphql`
query BLOG_POST_LISTING {
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
filter: {
frontmatter: { draft: { ne: true } }
fileAbsolutePath: { regex: "/posts/" }
}
) {
edges {
node {
frontmatter {
title
slug
date(formatString: "MMMM DD, YYYY")
tags
abstract
}
excerpt
id
}
}
}
}
`
const BlogPostListing = () => (
<StaticQuery
query={BLOG_POST_LISTING}
render={({ allMarkdownRemark }) =>
allMarkdownRemark.edges.map(({ node }) => (
<BlogPostListingItem post={node} key={node.id} />
))
}
/>
)
export default BlogPostListing
Here's the code for BlogPostListingItem.js
:
import React from 'react'
import { Link } from 'gatsby'
const BlogPostListingItem = ({ post }) => (
<div>
<article>
<div>
<Link to={`blog/${post.frontmatter.slug}`}>
<h2>{post.frontmatter.title}</h2>
</Link>
<p>{post.frontmatter.date}</p>
</div>
<main>
<p>
<em>
{post.frontmatter.abstract
? post.frontmatter.abstract
: post.excerpt}
</em>
</p>
</main>
<aside>
{post.frontmatter.tags.split(', ').map((tag, i) => (
<span>{tag + (i < tag.length - 1 ? ',' : '')}</span>
))}{' '}
</aside>
</article>
<hr />
</div>
)
export default BlogPostListingItem
Create the blog page in src/pages/blog.js
import React from 'react'
import BlogPostListing from '../components/BlogPostListing'
export default function blog() {
return (
<>
<BlogPostListing />
</>
)
}
Now, if you navigate to http://localhost:8000/blog
, you should see a list of all the blog posts.
However, we haven't created the pages for the actual blogposts yet, so clicking the links will fail.
Create the BlogPost
component inside of src/components/
.
Since this will actually be rendered as a page component, and since we'll want to be able pass an id variable to determine the specific blogpost to query for, we should use Gatsby's page query in this case.
The query will be something like this:
query PostQuery($id: String!) {
markdownRemark(id: { eq: $id }) {
frontmatter {
title
date(formatString: "MMMM Do, YYYY")
slug
tags
abstract
}
html
}
}
The page component file must export the query as query
. By doing this, Gatsby will expose the query data
to the default component's props
.
We can set the body of the post by creating an element with the attribute dangerouslySetInnerHTML
and setting the queried html as the value to __html
.
import React from 'react'
import { graphql } from 'gatsby'
export default function BlogPost({ data }) {
const { markdownRemark } = data
return (
<article>
<header>
<h1>{markdownRemark.frontmatter.title}</h1>
<p>{markdownRemark.frontmatter.date}</p>
</header>
<p>
<em>{markdownRemark.frontmatter.abstract}</em>
</p>
<hr />
<main dangerouslySetInnerHTML={{ __html: markdownRemark.html }} />
<aside>
{post.frontmatter.tags.split(', ').map((tag, i) => (
<span>{tag + (i < tag.length - 1 ? ',' : '')}</span>
))}{' '}
</aside>
<hr />
</article>
)
}
export const query = graphql`
query PostQuery($id: String!) {
markdownRemark(id: { eq: $id }) {
frontmatter {
title
date(formatString: "MMMM Do, YYYY")
slug
tags
abstract
}
html
}
}
`
But where do we create the routes for blogposts and how do we tell Gatsby to render them with this component?
Gatsby creates actual pages for each route and we can tell it how to create the pages by creating a hook in gatsby-node.js
.
Export a createPages
function, destructuring graphql
and actions
from its parameter.
Return a new Promise
in which you pass a graphql
query to the graphql
function. This function returns a promise. Chain then
to it, destructuring data
and errors
in the callback parameters.
Return reject(errors)
if there are errors
.
Loop over data.posts.edges
with forEach
.
For each edge, execute createPage
, passing in an object with the path
, component
to render (./src/components/BlogPost.js
in this case), and optionally, the context
. It's inside of context
that we'll pass the post's id
which will be used by page query (PostQuery
) to query for each specific post.
Finally, call resolve()
to resolve the promise.
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
const path = require('path')
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
graphql(`
query {
blogposts: allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
filter: {
frontmatter: { draft: { ne: true } }
fileAbsolutePath: { regex: "/posts/" }
}
) {
edges {
node {
frontmatter {
slug
}
id
}
}
}
}
`).then(({ data, errors }) => {
if (errors) {
return reject(errors)
}
data.blogposts.edges.forEach(({ node }) => {
const { slug } = node.frontmatter
const relativePath = `/blog/${slug}`
createPage({
path: relativePath,
component: path.resolve('./src/components/BlogPost.js'),
context: {
id: node.id,
},
})
})
resolve()
})
})
}
Rerun gatsby develop
. This time, it will build all the pages. Go to the blog page, click on a link. This should navigate you to blog/
+ the post slug
, where you should be able to see the markdown rendered as HTML.
In the next post on this series, I might explore handling multiple post types with gatsby-transformer-remark
, parsing tags, implementing page transitions with gatsby-plugin-layout
, pagination, deploying to Netlify
, and setting up Netlify CMS
.