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!
What we will do
- 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.
- In the root of the project run
yarn
to install dependencies - Run
yarn develop
- Open http://localhost:8000/___graphql
- You know the drill! Let's run this query
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??
HTML
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.
Excerpt
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.markdownRemarkreturn (
<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!
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!
Let's configure Gatsby!
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.
Let's go through what's happening!
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 addedblogPosts:
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).
Let's Create A Blog Post Template!
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…
Why would you want to do that?
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!