Categories
Gatsby.js

Gatsby.js — Tags and Post Page

Gatsby is a static web site framework that’s based on React.

We can use it to create static websites from external data sources and more.

In this article, we’ll look at how to create a site with Gatsby.

Tags Pages for Blog Posts

We can create tag pages for blog posts.

To do this, we work on a few files:

gatsby-config.js

module.exports = {
  plugins: [
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/src/content`,
      },
    },
  ],
}

gatsby-node.js

const path = require("path")
const _ = require("lodash")
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}
exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve("src/templates/post.js")
  const tagTemplate = path.resolve("src/templates/tags.js")
  const result = await graphql(`
    {
      postsRemark: allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 2000
      ) {
        edges {
          node {
            fields {
              slug
            }
            frontmatter {
              tags
            }
          }
        }
      }
      tagsGroup: allMarkdownRemark(limit: 2000) {
        group(field: frontmatter___tags) {
          fieldValue
        }
      }
    }
  `)
  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }
  const posts = result.data.postsRemark.edges
  posts.forEach(({ node }) => {
    const slug = node.fields.slug
    createPage({
      path: slug,
      component: blogPostTemplate,
      context: { slug },
    })
  })
  const tags = result.data.tagsGroup.group
  tags.forEach(tag => {
    createPage({
      path: `/tags/${_.kebabCase(tag.fieldValue)}/`,
      component: tagTemplate,
      context: {
        tag: tag.fieldValue,
      },
    })
  })
}

src/templates/tags.js

import React from "react"
import PropTypes from "prop-types"
import { Link, graphql } from "gatsby"

const Tags = ({ pageContext, data }) => {
  const { tag } = pageContext
  const { edges, totalCount } = data.allMarkdownRemark
  const tagHeader = `${totalCount} post${totalCount === 1 ? "" : "s"
    } tagged with "${tag}"`
  return (
    <div>
      <h1>{tagHeader}</h1>
      <ul>
        {edges.map(({ node }) => {
          const { slug } = node.fields
          const { title } = node.frontmatter
          return (
            <li key={slug}>
              <Link to={slug}>{title}</Link>
            </li>
          )
        })}
      </ul>
      <Link to="/tags">All tags</Link>
    </div>
  )
}
Tags.propTypes = {
  pageContext: PropTypes.shape({
    tag: PropTypes.string.isRequired,
  }),
  data: PropTypes.shape({
    allMarkdownRemark: PropTypes.shape({
      totalCount: PropTypes.number.isRequired,
      edges: PropTypes.arrayOf(
        PropTypes.shape({
          node: PropTypes.shape({
            frontmatter: PropTypes.shape({
              title: PropTypes.string.isRequired,
            }),
            fields: PropTypes.shape({
              slug: PropTypes.string.isRequired,
            }),
          }),
        }).isRequired
      ),
    }),
  }),
}
export default Tags
export const pageQuery = graphql`
  query($tag: String) {
    allMarkdownRemark(
      limit: 2000
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { tags: { in: [$tag] } } }
    ) {
      totalCount
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
          }
        }
      }
    }
  }
`

src/templates/post.js

import React from "react"
import { graphql } from "gatsby"

export default function BlogPost({ data }) {
  const post = data.markdownRemark
  return (
    <div>
      <h1>{post.frontmatter.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </div>
  )
}
export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

src/content/post.md

---
title: "A Trip To the Zoo"
tags: ["animals", "Chicago", "zoos"]
date: "2020-01-01"
---
I went to the zoo today. It was terrible.

In gatsby-config.js , we add the gatsby-source-filesystem plugin to read the Markdown files from the src/content folder.

And we add the gatsby-transformer-remark plugin to transform the Markdown to HTML.

Next, in gatsby-node.js , we create the slug field with the onCreateNode method.

We call the createFilePath function to create the slug field value from the file path.

Then we call createNodeField to create the slug field with the object with name set to 'slug' .

The value is the slug string that we created from createFilePath .

In the createPages function, we maker the query to get the post data.

The result.data.postRemark.edges has the posts data.

We call forEach on it so that we can call createPage on each entry in the callback to create the pages.

node.field.slug has the slug value we created from onCreateNode .

component is set to the blogPostTemplate so that we can display the site.

Then to get the tags, we use the result.data.tagsGroup.group property.

We pass in the slug to the context property of in the first forEach to get that data to the page.

We call forEach with a callback that calls the createPage function again.

But this time, we use it to create the tags page.

The path is the URL for the tags page.

component has the template we use to render the tags page.

context has the data we want to render on the tags page.

In tags.js , we have the tags page template.

We have the pageQuery object to make the query for the items with the given tag.

Then in the tags component, we get all the returned data from the data prop.

edges have the items with the given tag.

We call map to render the returned content.

The slug property has the slug. title has the title.

We have the Link component for each entry so we can go to the page with the given slug.

In post.js , we render the page.

We get the data from the data prop’s markdownRemark property.

Then we see the data displayed.

We have the query object to make the query to get the post with the given slug.

Conclusion

We can add a tags page to display the Markdown file with the given tags with a post page to display the Markdown file with a given slug with Gatsby.

Categories
Gatsby.js

Gatsby.js — useStaticQuery and Slugs

Gatsby is a static web site framework that’s based on React.

We can use it to create static websites from external data sources and more.

In this article, we’ll look at how to create a site with Gatsby.

Query with useStaticQuery Hook

We can use the useStaticQuery hook to query for data.

For example, we can write:

gatsby-config.js

module.exports = {
  siteMetadata: {
    title: "My Homepage",
    description: "This is where I write my thoughts.",
  },
}

src/pages/index.js

import React from "react"
import { graphql, useStaticQuery } from 'gatsby'

export default function Home() {
  const data = useStaticQuery(graphql`
    query HeaderQuery {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)

  return (
    <header>
      <h1>{data.site.siteMetadata.title}</h1>
    </header>
  )
}

We have the site’s metadata in gatsby-config.js .

Then we can query for them in the index.js file.

We pass in our GraphQL query into the useStaticQuery hook.

And that returns the data that we need.

Then we render the title in the JSX.

We can create our own hooks with the useStaticQuery hook.

For instance, we can write:

import React from "react"
import { graphql, useStaticQuery } from 'gatsby'

const useSiteMetadata = () => {
  const { site } = useStaticQuery(
    graphql`
      query SiteMetaData {
        site {
          siteMetadata {
            title
            description
          }
        }
      }
    `
  )
  return site.siteMetadata
}

export default function Home() {
  const { title, description } = useSiteMetadata()

  return (
    <header>
      <h1>{title}</h1>
      <h1>{description}</h1>
    </header>
  )
}

We used the useStaticQuery hook in the useSiteMetadata hook.

We return the siteMetadata and we use that to render the title and description properties of the metadata in gatsby-config.js .

GraphQL Fragments

GraphQL fragments are reusable pieces of a query.

To create a fragment, we use the fragment keyword.

For example, we can write:

import React from "react"
import { graphql } from 'gatsby'

export const query = graphql`
  fragment SiteInformation on Site {
    siteMetadata {
      title
      description
    }
  }

  query {
    site {
      ...SiteInformation
    }
  }
`
export default function Home({ data }) {
  return (
    <div>
      <h1>{data.site.siteMetadata.title}</h1>
      <p>{data.site.siteMetadata.siteDescription}</p>
    </div>
  )
}

We add the fragment into our query GraphQL query object.

We use the ... operator to apply the fragment in our query.

Then we render the result by getting the data from the data prop.

Creating Slugs for Pages

We can create slugs for our pages.

For example, we can write:

gatsby-node.js

const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

We check the node’s type.

If it’s 'MarkdownRemark' , then we create the slug with the createFilePath method.

Then we call createNodeField to add the slug field into our query.

Then in GraphiQL, we can query for it by writing:

{
  allMarkdownRemark {
    edges {
      node {
        fields {
          slug
        }
      }
    }
  }
}

Then we should get a result that looks like:

{
  "data": {
    "allMarkdownRemark": {
      "edges": [
        {
          "node": {
            "fields": {
              "slug": "/post/"
            }
          }
        }
      ]
    }
  },
  "extensions": {}
}

returned.

Conclusion

We can use the useStaticQuery hook to query for data in our Gatsby project.

Also, we can create fragments and slugs in our project.

Categories
Gatsby.js

Gatsby.js — 404 Page and SEO

Gatsby is a static web site framework that’s based on React.

We can use it to create static websites from external data sources and more.

In this article, we’ll look at how to create a site with Gatsby.

Add a 404 Page

We can create a 404 page by adding a 404.js file in the src/pages folder.

For example, we can write:

src/pages/404.js

import React from "react"

const NotFoundPage = () => {
  return <div>not found</div>
}

export default NotFoundPage;

to add this file.

Now we should see this page when we go to any unrecognized URL in production.

SEO

SEO is an important part of many sites.

Gatsby provides support for this with React Helmet.

We have to install React Helmet by running:

npm i react-helmet

Then we can write:

gatsby-config.js

module.exports = {
  siteMetadata: {
    title: "My Site",
    titleTemplate: "%s · The Real Hero",
    description: "Some website",
    url: "https://www.example.com",
    image: "/assets/dog.jpg",
    twitterUsername: "@occlumency",
  },
}

src/pages/index.js

import React from "react"
import PropTypes from "prop-types"
import { Helmet } from "react-helmet"
import { useLocation } from "@reach/router"
import { useStaticQuery, graphql } from "gatsby"

const SEO = ({ title, description, image, article }) => {
  const { pathname } = useLocation()
  const { site } = useStaticQuery(query)
  const {
    defaultTitle,
    titleTemplate,
    defaultDescription,
    siteUrl,
    defaultImage,
    twitterUsername,
  } = site.siteMetadata
  const seo = {
    title: title || defaultTitle,
    description: description || defaultDescription,
    image: `${siteUrl}${image || defaultImage}`,
    url: `${siteUrl}${pathname}`,
  }
  return (
    <Helmet title={seo.title} titleTemplate={titleTemplate}>
      <meta name="description" content={seo.description} />
      <meta name="image" content={seo.image} />
      {seo.url && <meta property="og:url" content={seo.url} />}
      {(article ? true : null) && <meta property="og:type" content="article" />}
      {seo.title && <meta property="og:title" content={seo.title} />}
      {seo.description && (
        <meta property="og:description" content={seo.description} />
      )}
      {seo.image && <meta property="og:image" content={seo.image} />}
      <meta name="twitter:card" content="summary_large_image" />
      {twitterUsername && (
        <meta name="twitter:creator" content={twitterUsername} />
      )}
      {seo.title && <meta name="twitter:title" content={seo.title} />}
      {seo.description && (
        <meta name="twitter:description" content={seo.description} />
      )}
      {seo.image && <meta name="twitter:image" content={seo.image} />}
    </Helmet>
  )
}

SEO.propTypes = {
  title: PropTypes.string,
  description: PropTypes.string,
  image: PropTypes.string,
  article: PropTypes.bool,
}
SEO.defaultProps = {
  title: null,
  description: null,
  image: null,
  article: false,
}

const query = graphql`
  query SEO {
    site {
      siteMetadata {
        defaultTitle: title
        titleTemplate
        defaultDescription: description
        siteUrl: url
        defaultImage: image
        twitterUsername
      }
    }
  }
`

const IndexPage = () => {
  return <>
    <SEO />
    <div>hello world</div>
  </>
}

export default IndexPage

In gatsby-config.js , we have the website’s metadata.

The metadata is in the siteMetadata property.

title has the site’s title.

titleTemplate has the title template. %s is the placeholder for the title.

description has the site’s description.

url has the site’s URL. image has the site’s preview image.

twitterUsername has the Twitter username for the site.

Then in index.js , we create the SEO component.

It uses the Helmet component to add the title , titleTemplate , description , url , and twitterUsername from the site.siteMetadata property.

The data comes from the query object that we have at the bottom of the file.

We call the useStaticQuery hook with the query object to make the query.

We pass all the metadata into the Helmet component and they’ll be rendered in the head tag.

Conclusion

We can use the React Helmet package to render our site’s metadata in the head tag for SEO purposes.

Also, we can create a 404 page easily with Gatsby.

Categories
Gatsby.js

Gatsby.js — Render One or More Markdown Files

Gatsby is a static web site framework that’s based on React.

We can use it to create static websites from external data sources and more.

In this article, we’ll look at how to create a site with Gatsby.

Adding Markdown Pages

We can add pages from Markdown by adding the gatsby-transformer-remark and gatsby-source-filesystem plugins.

To do this, we write:

gatsby-config.js

module.exports = {
  plugins: [
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/src/content`,
      },
    },
  ],
}

gatsby-node.js

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions
  const blogPostTemplate = require.resolve(`./src/templates/post.js`)
  const result = await graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              slug
            }
          }
        }
      }
    }
  `)

  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }
  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.frontmatter.slug,
      component: blogPostTemplate,
      context: {
        slug: node.frontmatter.slug,
      },
    })
  })
}

src/templates/post.js

import React from "react" { graphql } from "gatsby"
export default function Template({
  data,
}) {
  const { markdownRemark } = data
  const { frontmatter, html } = markdownRemark
  return (
    <div className="blog-post-container">
      <div className="blog-post">
        <h1>{frontmatter.title}</h1>
        <h2>{frontmatter.date}</h2>
        <div
          className="blog-post-content"
          dangerouslySetInnerHTML={{ __html: html }}
        />
      </div>
    </div>
  )
}
export const pageQuery = graphql`
  query($slug: String!) {
    markdownRemark(frontmatter: { slug: { eq: $slug } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        slug
        title
      }
    }
  }
`

src/content/post.md

---
title: My First Post
date: 2019-07-10
slug: /my-first-post
---
This is my first Gatsby post written in Markdown!

We add the gatsby-source-filesystem plugin to read the Markdown files from the src/content folder.

And we add the gatsby-transformer-remark plugin to transform the Markdown files to HTML.

Then in gatsby-node.js , we get the Markdown content with the allMarkdownRemark query.

We sort the entries by date with the sort values.

And we limit the number of entries to 1000.

And we specify that we return the slug in the result.

Then we call forEach to call the createPage function to create the pages.

The path is the URL path to access the page.

component has the template file path.

And context has the data that we want to display from the result entry.

In post.js , we have the query to get a single entry.

We get the entry with give slug with the markdownRemark query.

Then we get the html field to get the content.

frontmatter has the metadata for the page.

And we get that from the data prop’s markdownRemark property.

Finally, we render that in the JSX of Template .

Now we should see the post.md content displayed.

Add a List of Markdown Blog Posts

We can also add a page withn multiple posts.

To do this, we write:

src/page/index.js

import React from "react"
import { graphql } from "gatsby"
import { Link } from "gatsby"

const PostLink = ({ post }) => (
  <div>
    <Link to={post.frontmatter.slug}>
      {post.frontmatter.title} ({post.frontmatter.date})
    </Link>
  </div>
)

const IndexPage = ({
  data: {
    allMarkdownRemark: { edges },
  },
}) => {
  const Posts = edges
    .filter(edge => !!edge.node.frontmatter.date)
    .map(edge => <PostLink key={edge.node.id} post={edge.node} />)
  return <div>{Posts}</div>
}

export default IndexPage

export const pageQuery = graphql`
  query {
    allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
      edges {
        node {
          id
          excerpt(pruneLength: 250)
          frontmatter {
            date(formatString: "MMMM DD, YYYY")
            slug
            title
          }
        }
      }
    }
  }
`

We create the PostLink component to display a link for the post.

IndexPage has the posts.

We get the posts data from the data prop’s allMarkdownRemark.edges property.

And we map the entries to the PostLink component to render each entry with the PostLink component.

The pageQuery gets us the data for the data prop.

Conclusion

We can render one or more Markdown files in our Gatsby project.

Categories
Gatsby.js

Gatsby.js — Creating Pages from Data Programmatically

Gatsby is a static web site framework that’s based on React.

We can use it to create static websites from external data sources and more.

In this article, we’ll look at how to create a site with Gatsby.

Creating Pages from Data Programmatically

We can create pages from data programmatically with Gatsby.

For example, we can write:

gatsby-config.js

module.exports = {
  plugins: [
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/src/content`,
      },
    },
  ],
  siteMetadata: {
    title: "My Homepage",
    description: "This is where I write my thoughts.",
  },
}

src/content/post.md

---
title: My First Post
date: 2019-07-10
path: /my-first-post
---
This is my first Gatsby post written in Markdown!

gatsby-node.js

const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

exports.createPages = async function ({ actions, graphql }) {
  const { data } = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            fields {
              slug
            }
          }
        }
      }
    }
  `)
  data.allMarkdownRemark.edges.forEach(edge => {
    const slug = edge.node.fields.slug
    actions.createPage({
      path: slug,
      component: require.resolve(`./src/templates/post.js`),
      context: { slug },
    })
  })
}

src/templates/post.js

import React from "react"
import { graphql } from "gatsby"

export default function BlogPost({ data }) {
  const post = data.markdownRemark
  return (
    <div>
      <h1>{post.frontmatter.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </div>
  )
}
export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

First, we add the gatsby-transformer-remark and gatsby-source-filesystem plugins to read Markdown files and parse them to HTML.

We create the slug field with the createNode function.

We call createNodeField to create our slug.

With the if statement in onCreateNode , we only add the slug field if we query for MarkdownRemark entries.

Then in the createPages function, we make the query for the slug and then loop through each entry with forEach .

In the forEach callback, we call actions.createPage to add the pages from the entries returned.

path is set to the slug ,

component is set to the template file location.

context has the data we want to display in the template.

In post.js , we make the query for the post with the given slug.

Then we get the data from the data prop and render it in our JSX.

Now when we go to http://localhost:8000/post, we see:

My First Post

This is my first Gatsby post written in Markdown!

displayed.

Conclusion

We can create pages from data programmatically with Gatsby.

This way, we can create our pages from Markdown files and render them.