GatsbyJS Series — Create blog details pages dynamically (Part 5)

Jun 23, 2019

At the moment, we have done many features for our blog. However, we still can’t view one post details. We’ll complete this feature in this part together. Let’s start!

Based on our data from contentful, Gatsby support to create nodes (we can see them as blog details pages right now for easier understand) so that we can visit node’s details. Otherwise, whenever you click on any post, 404 page will be shown like this:

404 page not found
404 page not found

Creating slugs for pages

Create new page has 2 steps:

  1. Generate slug (or path) for the page.
  2. Create the page.

First, create gatsby-node.js file at root folder with the following content:

const path = require(`path`)

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions
  const blogPost = path.resolve(`./src/templates/blog-post.tsx`)

  return graphql(
    `
      {
        allContentfulBlogPost {
          edges {
            node {
              title
              slug
            }
          }
        }
      }
    `
  ).then(result => {
    if (result.errors) throw result.errors

    // Create blog posts pages.
    const posts = result.data.allContentfulBlogPost.edges

    posts.forEach((post, index) => {
      createPage({
        path: post.node.slug,
        component: blogPost,
        context: {
          slug: post.node.slug,
        },
      })
    })

    return null
  })
}

Note that in order to create a page successfully, we need data (query from graphql) and a template page. In this example it is src/templates/blog-post.tsx. Another thing to notice here is that we create slug and inject into context of page. So that we can use it as one page’s property later:

...
context: {
  slug: post.node.slug,
}
...

gatsby-transformer-remark

For the body of a blog, we need one plugin support for markdown syntax. gasby-transformere-remark here to help:

yarn add gatsby-transformer-remark

Add one more configuration to gatsby-config.js:

// previous code

module.exports = {
  plugins: [
    // previous configurations
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [],
      },
    },
  ],
}

Moreover, gasby-transformere-remark is able to help to format code or handle image responsive by combining with other plugins such as: gatsby-remark-images-contentful, gatsby-remark-prismjs, gatsby-remark-smartypants, etc.

Let’s install and configure them together:

yarn add gatsby-remark-prismjs gatsby-remark-images-contentful gatsby-remark-smartypants prismjs

Update gatsby-config.js:

// previous code

module.exports = {
  plugins: [
    // previous configurations
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-images-contentful`,
            options: {
              maxWidth: 1020,
              showCaptions: true,
            },
          },
          `gatsby-remark-prismjs`,
          `gatsby-remark-smartypants`,
        ],
      },
    },
  ],
}

Formatting code

In order to format code style, we need to add prismjs css to gatsby-browser.js file first:

import "prismjs/themes/prism-solarizedlight.css"

// custom typefaces
import "typeface-montserrat"
import "typeface-merriweather"

Blog post template

File src/templates/blog-post.tsx:

import React from 'react'
import { graphql } from 'gatsby'
import get from 'lodash/get'
import styled from 'styled-components'
import Image from 'gatsby-image'

import Layout from '@components/Layout'

interface IProps {
  pageContext: {
    slug: string
  }
  location: ILocation
  data: {
    postDetails: {
      title: string
      slug: string
      publishedDate: string
      description: string
      heroImage: {
        sizes: {
          aspectRatio: number,
          src: string,
          srcSet: string,
          sizes: string,
        }
      }
    }
  }
}

const StyledContainer = styled.div`
  .header-container {
    h1 {
      margin-top: 0;
    }

    .published-date {
      ${props => ({ ...props.theme.scale(-0.5) })}
      margin-top: ${props => props.theme.rhythm(-0.75)};
      color: ${props => props.theme.colors.gray600};
    }
  }

  .hero-image {
    margin: ${props => props.theme.rhythm(1.25)} 0;
  }

  .body-container {
    blockquote {
      ${props => ({ ...props.theme.scale(0.5) })}
      border-left-color: ${props => props.theme.colors.primary};
    }

    img {
      width: 100%;
    }
  }
`

class BlogPostTemplate extends React.Component<IProps, {}> {
  render() {
    const post = get(this.props, 'data.postDetails')

    return (
      <Layout location={this.props.location}>
        <StyledContainer>
          <div className="header-container">
            <h1>{post.title}</h1>
            <div className="published-date">
              {post.publishedDate}
            </div>
            <div className="hero-image">
              <Image sizes={post.heroImage.sizes} alt="" />
            </div>
          </div>
          <div className="body-container"
            dangerouslySetInnerHTML={{ __html: post.body.childMarkdownRemark.html }}
          />
        </StyledContainer>
      </Layout>
    )
  }
}

export default BlogPostTemplate

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    postDetails: contentfulBlogPost(slug: { eq: $slug }) {
      title
      description
      heroImage {
        sizes(maxWidth: 700, maxHeight: 349, resizingBehavior: SCALE) {
          ...GatsbyContentfulSizes_withWebp
        }
      }
      slug
      publishedDate(formatString: "MMM DD, YYYY")
      tags
      body {
        childMarkdownRemark {
          html
        }
      }
    }
  }
`

Restart Gatsby server and take a look at our result:

Blog details page
Blog details page

Summary

Really cool, right? However, if you intend to the blog title on the browser tab, you’ll see that the name is not so good.

Not good title
Not good title

We’ll improve this one in next part and also adding some basic SEO component. See you there.