Categories
Gatsby.js

Gatsby.js — Custom RSS Feed

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.

Customize RSS Feed

We can customize our RSS feed by adding more plugin options into gatsby-config.js.

For example, we can write:

gatsby-config.js

module.exports = {
  plugins: [
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-plugin-feed`,
      options: {
        query: `
          {
            site {
              siteMetadata {
                title
                description
                siteUrl
                site_url: siteUrl
              }
            }
          }
        `,
        feeds: [
          {
            serialize: ({ query: { site, allMarkdownRemark } }) => {
              return allMarkdownRemark.edges.map(edge => {
                return Object.assign({}, edge.node.frontmatter, {
                  description: edge.node.excerpt,
                  date: edge.node.frontmatter.date,
                  url: site.siteMetadata.siteUrl + edge.node.fields.slug,
                  guid: site.siteMetadata.siteUrl + edge.node.fields.slug,
                  custom_elements: [{ "content:encoded": edge.node.html }],
                })
              })
            },
            query: `
              {
                allMarkdownRemark(
                  sort: { order: DESC, fields: [frontmatter___date] },
                ) {
                  edges {
                    node {
                      excerpt
                      html
                      fields { slug }
                      frontmatter {
                        title
                        date
                      }
                    }
                  }
                }
              }
            `,
            output: "/rss.xml",
            title: "Your Site's RSS Feed",
          },
        ],
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/src/content`,
      },
    },
  ],
  siteMetadata: {
    siteUrl: `https://www.example.com`,
  },
}

We need siteMetadata.siteUrl to generate the RSS feed.

gatsby-node.js

const path = require("path")
const _ = require("lodash")
const { createFilePath } = require(`gatsby-source-filesystem`)

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

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve("src/templates/post.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 },
    })
  })
}

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
      }
    }
  }
`

We add the gatsby-plugin-feed plugin into gatsby-config.js .

We install the plugin by running:

npm i gatsby-plugin-feed

We add some options into our config file. The options.query property has the string for the GraphQL query to get the site’s metadata.

The feeds property has the serialize method to get the data from the query and return an object for each entry that’s retrieved.

description has the description of the post.

date has the date of the post.

url has the URL of the post.

guid is a unique ID we generate.

custom_elements have extra elements that we want to include in the RSS feed.

feeds.query has the query to get the posts.

output has the path for the RSS feed file.

title has the feed’s title.

In gatsby-node.js , we make the GraphQL query to get the Markdown posts and call createPage to create the posts.

And post.js displays the post by getting the data from the data prop.

Now when we run npm run build , we see the public/rss.xml file generated.

We should see the item element with the title , description , link , guid , pubDate , and content:encoded elements.

Conclusion

We can create a custom RSS feed for our site with Gatsby.

Categories
Gatsby.js

Gatsby.js — Sitemap and RSS Feed

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.

Sitemap

We can use the gatsby-plugin-sitemap plugin to add a sitemap.

To do this, we write:

module.exports = {
  siteMetadata: {
    siteUrl: `https://www.example.com`,
  },
  plugins: [`gatsby-plugin-sitemap`],
}

in gatsby-config.js .

We install the plugin by running:

npm i gatsby-plugin-sitemap

siteUrl has the base URL of the website. This is required.

And we add the plugin into the plugins array.

Then when we make the production build with npm run build , we’ll get the sitemap.

RSS Feed

We can add an RSS feed with the gatsby-plugin-feed plugin.

To do this, we write:

gatsby-config.js

module.exports = {
  plugins: [
    `gatsby-transformer-remark`,
    `gatsby-plugin-feed`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `content`,
        path: `${__dirname}/src/content`,
      },
    },
  ],
  siteMetadata: {
    siteUrl: `https://www.example.com`,
  },
}

We need siteMetadata.siteUrl to generate the RSS feed.

gatsby-node.js

const path = require("path")
const _ = require("lodash")
const { createFilePath } = require(`gatsby-source-filesystem`)

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

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve("src/templates/post.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 },
    })
  })
}

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
      }
    }
  }
`

We add the gatsby-plugin-feed plugin into gatsby-config.js .

We install the plugin by running:

npm i gatsby-plugin-feed

In gatsby-node.js , we make the GraphQL query to get the Markdown posts and call createPage to create the posts.

And post.js displays the post by getting the data from the data prop.

Now when we run npm run build , we see the public/rss.xml file generated.

Conclusion

We can add a sitemap and RSS feed to our website with Gatsby.

Categories
Gatsby.js

Gatsby.js — Pagination

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.

Pagination

We can add pagination for our pages with Gatsby.

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

const path = require("path")
const { createFilePath } = require("gatsby-source-filesystem")
exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions
  const result = await graphql(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                slug
              }
            }
          }
        }
      }
    `
  )
  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

const posts = result.data.allMarkdownRemark.edges
  const postsPerPage = 6
  const numPages = Math.ceil(posts.length / postsPerPage)
  Array.from({ length: numPages }).forEach((_, i) => {
    createPage({
      path: i === 0 ? `/blog` : `/blog/${i + 1}`,
      component: path.resolve("./src/templates/blog-list-template.js"),
      context: {
        limit: postsPerPage,
        skip: i * postsPerPage,
        numPages,
        currentPage: i + 1,
      },
    })
  })
}
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

src/templates/blog-list-template.js

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

export default class BlogList extends React.Component {
  render() {
    const posts = this.props.data.allMarkdownRemark.edges
    return (
      <div>
        {posts.map(({ node }) => {
          const title = node.frontmatter.title || node.fields.slug
          return <div key={node.fields.slug}>{title}</div>
        })}
      </div>
    )
  }
}
export const blogListQuery = graphql`
  query blogListQuery($skip: Int!, $limit: Int!) {
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      limit: $limit
      skip: $skip
    ) {
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
          }
        }
      }
    }
  }
`

We add the gatsby-source-filesystem plugin to read Markdown files from the file system.

Then we use the gatsby-transformer-remark plugin to parse the Markdown file into HTML.

The gatsby-node.js file gets the Markdown posts with a GraphQL query.

Then create an array with the length being the number of pages.

Then we loop through them with forEach and call createPage in the callback to pass in the path to the blog-list-template file along with other data in the context.

The context property has more data that we pass into the blog-list-template file.

They include the postsPerPage , the number of items to skip, numPages which is the number of pages, and the currentPage .

In the blog-list-template.js file, we make the blogListQuery with the skip and limit parameters to get the items from a given page.

In the BlogList component, we call map on posts to render the post title.

We read the Markdown files from the src/content folder.

Then when we go to http://localhost:8000/blog, we see the first page.

http://localhost:8000/blog has the 2nd page and so on.

Conclusion

We can add pagination easily with Gatsby.

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.