Gatsby Blog + Netlify CMS Tutorial — Part 2

Guide Table Of Contents

In the last part, we focused on how to use Gatsby GQL Data layer to make various queries on our markdown files using the GraphiQL view, and make adjustments to the GQL fields via gatsby-config.js

In this part, we will begin to integrate React into the mix!

  • Query data using GQL and display it in a static Gatsby page
  • Create a Gatsby page template
  • Dynamically generate based on markdown files
  • Create a page to display a list of our blogs
  • Add a featured image to our blog posts

Note: This tutorial is not focusing on styling at all, so it will be kept very basic, as we are only focusing on the functionality of the tooling

Tutorial Preparation

Click HERE to get the start state of the branch we will work for this tutorial.

The branch is pretty much the same as the end state of part one, I have just added the following.

  • Pre-created 3 markdown files for us to fetch data from
  • Added some images into a folder which we will use to render

Let's Get Started!

So in this tutorial, we will be using Pokemon as our sample data, I picked Squirtle, Bulbasaur, and Pikachu as our 3 starters, sorry Charmander!!

We will create a very simple blog (poke-blog), the content will be minimal, but it will at least provide enough context on how to create something more in-depth.

query MyQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
category
height
intro
title
weight
}
html
excerpt
}
}
}
}

So this should all be pretty familiar from the last tutorial, except now we have two new fields, HTML and excerpt (mmm shiny), let’s discuss these before moving forward.

What’s that field type??

If you go to the markdown files in content/blog-posts you’ll notice that under the frontmatter (content enclosed in —-) data we have some markdown.

This data is fetched by the HTML field (provided by the gatsby-transformer-remark plugin). Notice how in our GQL result this is automagically converted into HTML for us. The gatsby-transformer-remark plugin does all this work for us! So we can render it easily in our react components without the need of a 3rd party library.

Once again the gatsby-transformer-plugin is at work here doing its wonders. It provides us with an excerpt of our markdown. By default, this will trim the markdown to 140 characters but can be overridden by using the following techniques.

Prune Length
If we add the pruneLength variable to the excerpt field we can easily override the value of when the markdown will be trimmed, as shown below.

excerpt(pruneLength: 500)

Excerpt Separator
At times a single value for all the markdown may not be appropriate, we are able to define the cutoff manually on each markdown file ourselves by using the excerpt separator, which is implemented as follows

  • Navigate to gatsby-config.js
  • Find the gatsby-transformer-remark object
  • Modify it to the following
{
resolve: `gatsby-transformer-remark`,
options: {
excerpt_separator: `<!-- Bulbasaur used cut! -->`
}
}

Note: The string used inbetween <!-- --> can be any value you like

Now in your markdown files, you just need to add your excerpt separator anywhere, and the gatsby-transformer-remark will do all the work. If an excerpt separator is not found, then pruneLength will just use its default value.

Example

## DescriptionSquirtle is a small Pokémon that resembles a light blue turtle. While it typically walks on its two short legs, it has been shown to run on all fours in Super Smash Bros. <!-- Bulbasaur used cut! --> Brawl. It has large, purplish or reddish eyes and a slightly hooked upper lip. Each of its hands and

The gatsby-transformer-remark has other configuration options also, feel free to read more about it HERE.

Let's Render Our Data!

Okay so let's get React involved (finally), so let's do the following!

  • Navigate to pages/index.js and modify the component as below
import React from "react"
import { graphql } from "gatsby"
import Layout from "components/layout"
import SEO from "components/seo"
const styles = {fontWeight: 800}const IndexPage = ({ data }) => {
const {
category,
height,
intro,
title,
weight
} = data.markdownRemark.frontmatter
const { html } = data.markdownRemark
return (
<Layout>
<SEO title="Home" />
<h2>Pokemon: {title}</h2>
<p>{intro}</p>
<h4>Stats</h4>
<ul>
<li><span style={styles}>Height:</span> {height}</li>
<li><span style={styles}>Weight:</span> {weight}</li>
<li><span style={styles}>Category:</span> {category}</li>
</ul>
<div dangerouslySetInnerHTML={{ __html: html }} />
</Layout>
)
}
export default IndexPageexport const IndexQuery = graphql`
query PokemonQuery {
markdownRemark(fields: { slug: { eq: "pikachu" } }) {
frontmatter {
category
height
intro
title
weight
}
html
}
}
`;

With the component above, your page should now look like the image below!

Data! Glorious data!

Now hopefully the above component should be pretty straight forward, as we are not using any new concepts. The only thing we need to be aware of here when using the Graphql function in a page component is

  • The GQL query must be exported from the component.
  • The GQL results will be passed as a data prop into the component.

This is great and all, we have rendered our first markdown page in a React component! Though this method is not entirely useful as we are making a blog, we will need to generate this content dynamically, based on the URL.

The example above was simply to get a feel on how the data is handled when fetched via GraphQL, lets now make this into something more realistic!

Gatsby Page Templates

Blogs will naturally have many different articles which required to be rendered, and due to the way Gatsby works, by fetching all the required data at build time via the GQL queries, we need a way to generate these pages at build time so when a user goes to the URL /blog/pikachu or /blog/bulbasaur the relative markdown page is rendered.

We can easily achieve this with Gatsby's concept of templates!

To do this we will make use of Gatsby’s createPages function, I suggest you read more about this HERE, as we will only briefly go over this function.

  • Navigate to gatsby-node.js
  • Under the other functions add the following code
exports.createPages = async ({ graphql, actions }) => {
// You could keep the GQL Query in here - I prefer to separate
const { data } = await getPageData(graphql)

data.blogPosts.edges.forEach(({ node }) => {
const {slug} = node.fields;
actions.createPage({
path: `/blog/${slug}`,
component: path.resolve("./src/templates/blog-post-template"),
context: {slug},
})
})
}
async function getPageData(graphql) {
return await graphql(`
{
blogPosts: allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)}
  • Your gatsby-node.js file should look like THIS.

The first thing we are doing inside the createPages function is running a GQL query to fetch the data we require for generating dynamic pages. The GQL query is only fetching the slug.

This is all we need so we can create the page path, such as for the bulbasaur slug it will allow us to create a path such as /blog/bulbasaur .

Note: In the above GQL query before allMarkdownRemark i added blogPosts: you can think this as a variable, because eventually we may add more to this GQL query, and we can easily separate the data using variables.

Once we have the result of our GQL query we can then use this to dynamically generate our pages. We use data.blogPosts.edges to loop over each result (which is currently 3 items, as we only have 3 markdown files).

We use the createPage function to then dynamically create our page, and we pass in the following properties

  • path: This will be the URL path of the page i.e /blog/bulbasaur
  • template: This is the React JS template used to display the page
  • context: We can pass in variables, which can be used in the GQL Query that is in the template page (You will see this in action soon).

With the above function in place, Gatsby will be able to generate dynamic pages, but we still need to create a template, so create a file called blog-post-template.js in src/templates (You will need to make the templates folder).

In this new file, paste the following code

import React from "react"
import { graphql } from "gatsby"
import Layout from "components/layout"
import SEO from "components/seo"
const styles = {fontWeight: 800}const PokemonTemplate = ({ data }) => {
const {
category,
height,
intro,
title,
weight,
} = data.markdownRemark.frontmatter

const { html } = data.markdownRemark
return (
<Layout>
<SEO title={`Pokemon - ${title}`} />
<h2>Pokemon: {title}</h2>
<p>{intro}</p>
<h4>Stats</h4>
<ul>
<li><span style={styles}>Height:</span> {height}</li>
<li><span style={styles}>Weight:</span> {weight}</li>
<li><span style={styles}>Category:</span> {category}</li>
</ul>
<div dangerouslySetInnerHTML={{ __html: html }} />
</Layout>
)
}
export default PokemonTemplateexport const PokemonTemplateQuery = graphql`
query PokemonPageQuery($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
frontmatter {
category
height
intro
title
weight
}
html
}
}
`;

The only changes we need to do are the following:

  • Change the GQL Query, notice how we added a slug variable in our GQL Query, this is coming from the createPage function we created earlier, its the name of the variable which we passed in the context.
  • I changed the query name to PokemonPageQuery, this is because the index.js is still using PokemonQuery which would clash when Gatsby is building. These query names can be called anything you like.
  • Changed the title prop being passed into the SEO component
  • Changed the component name

That's it!

If you now restart the development environment, you should be able to visit the http://localhost:8000/blog/pikachu page, which will render the data for Pikachu.

Try changing the URL to /blog/squirtle and /blog/bulbasaur and it should display the relevant data!

Display a List Of All Blog Posts

Now we are able to dynamically view all of our blog posts, let's change the index.js page to display a list of all our blogs.

  • Navigate to src/pages/index.js and paste the following
import React from "react"
import { graphql, navigate } from "gatsby"
import Layout from "components/layout"
import SEO from "components/seo"
const styles = {
margin: 10,
marginLeft: 0,
border: "2px solid black",
padding: 10
}
const IndexPage = ({ data }) => {
return (
<Layout>
<SEO title="Home" />
<h2>Pokemon Blog</h2>
{renderBlogs(data.allMarkdownRemark.edges)}
</Layout>
)
function renderBlogs(posts) {
return posts.map(item => {
const { slug } = item.node.fields
const { title, intro } = item.node.frontmatter
return (
<div style={styles} onClick={() => navigate(`/blog/${slug}`)}>
<div style={{ color: "black" }}>
<h4>{title}</h4>
<p>{intro}</p>
</div>
</div>
)
})
}
}
export default IndexPageexport const IndexQuery = graphql`
{
allMarkdownRemark {
edges {
node {
frontmatter {
title
intro
}
fields {
slug
}
}
}
}
}
`

The page should now look like the following!
Clicking on any of the boxed divs will now take you to the post

The concepts to create this we have already covered, and the way the data is rendered is as you would any other array of data in React so hopefully, the code above should be self-explanatory.

This is great and all, but we are missing one thing, Images! Lets spice up the look of our posts with a featured image.

Adding Images With Frontmatter

For handling our Images using gatsby-plugin-sharp provides a lot of functionality out of the box, which we can take advantage of, I suggest you read more about the plugin HERE.

When working with images in frontmatter, by default it's expected that the images are in the same directory as the markdown, but this is not always the case, as with our example, so some modification will be required.

  • Navigate to gatsby-config.js
  • We have already added a gatsby-source-filesystem path for our images, this is required for our GQL queries to work with images.
  • In our markdown files, we then import the image via a relative path, such as the example below
---
title: Bulbasaur
intro: Bulbasaur can be seen napping in bright sunlight. There is a seed on its back. By soaking up the sun's rays, the seed grows progressively larger.
category: Seed
height: 2' 04"
weight: 15.2lbs
featuredImage: ../../src/images/bulbasaur.png
---
...markdown

Now we have added the featuredImage property we can now query this image via GQL within our components, you may be wondering…

We could just import images as we normally do with React, and for a lot of cases we still will be doing that, but for cases where the images are in the context of the blog post, doing it via GQL Query is the sanest way. We also get a lot of functionality as the library is simply wrapping around the Sharp image processing library. It allows us to

  • Easily compress image size and quality
  • Resize images
  • Make images responsive
  • Grayscale images
  • and much more, I suggest you read about the plugin in the link I provided

Lets now go to our src/pages/index.js page and modify our GQL query to the following

export const IndexQuery = graphql`  {
allMarkdownRemark {
edges {
node {
frontmatter {
title
intro
featuredImage {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
}
}
}
fields {
slug
}
}
}
}
}
`

In our component, we need to make use of the gatsby-image library, which we import at follows

import Img from "gatsby-image"

Then we need to make a slight change to the renderBlogs function

function renderBlogs(posts) {
return posts.map(item => {
const { slug } = item.node.fields
const { title, intro, featuredImage } = item.node.frontmatter

return (
<div style={styles} onClick={() => navigate(`/blog/${slug}`)}>
<div style={{ color: "black" }}>
<h4>{title}</h4>
<p>{intro}</p>
<Img fixed={featuredImage.childImageSharp.fixed} />
</div>
</div>
)
})
}

Notice the fixed the property we are using on the Img component, If we were querying the image using ...GatsbyImageSharpFluid we would then use the component as shown below

<Img fluid={featuredImage.childImageSharp.fluid} />

I suggest you read more about these plugins as they are out of the scope of this tutorial

After adding the Image on our Image page, please go ahead and give it a go and add it to the src/templates/blog-post-template.js file!

The End

Right, that's the end of this part of the tutorial!

Hopefully, you were able to follow along, learn some more and everything is working as expected! In case you had any issues following along, HERE is the end state of the application.

Next part of the tutorial we will start looking into implementing Netlify CMS so we can generate new blog posts via the CMS!