How To Create Future Blog Posts In Gatsby

React

18/03/2021


As any blogger on a schedule will tell you, writing content in advance is important. Unfortunately, this isn't something that comes out-of-the-box with Gatsby and Markdown, so some tinkering is needed.

Filtering out future dates

The idea is relatively simple. With the help of GraphQL, whenever we build our site, we can choose to not include any posts that are newer than the current time and day.

In our markdown file, we define the publishing date of the blog post in the frontmatter, i.e. the part between the dashed lines 😋.

MARKDOWN
---
title: "How to create future blog posts in Gatsby"
date: 2021-03-18
---

However, GraphQL doesn't offer any time-based filter. So instead, we need to create a new dynamic field for each blog post that evaluates to true or false depending on the post date. And how do we do that? While Gatsby creates its nodes, of course!

So in your gatsby-node.js file, you may add the following:

JAVASCRIPT
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === "Mdx") {
// Create scheduled post
const publishDate = node.frontmatter.date
const isScheduledPost = !publishDate || (new Date(publishDate)).getTime() > Date.now()
createNodeField({
node,
name: "isScheduledPost",
value: isScheduledPost,
})
}
}

While Gatsby is busy creating its nodes, we want to make sure our isScheduledPost field is only created on nodes belonging to markdown files, hence the node type verification. If we happen to not find any publishing date associated with a markdown file, we will assume it's a draft and therefore schedule it forever (until you remember to add a date, naturally 😉).

The next step is to query blog posts during the page creation where isScheduledPost evaluates to false.

JAVASCRIPT
exports.createPages = ({ actions, graphql }) => {
// ...
const result = await graphql(`
allMdx(
sort: { fields: [frontmatter___date], order: DESC },
filter: {
fields: { isScheduledPost: { eq: false } }
}
) {
edges {
node {
fields {
slug
}
frontmatter {
title
date
}
}
}
}
`)
// ...
}

More likely than not, you probably have an overview page that lists all your blog posts. Evidently, you'll also want to filter out any scheduled posts there. So similarly, in your page query:

JAVASCRIPT
export const pageQuery = graphql`
query BlogIndexQuery {
allMdx(
sort: { fields: [frontmatter___date], order: DESC },
filter: {
fields: { isScheduledPost: { eq: false } }
}
) {
edges {
node {
id
body
excerpt(pruneLength: 180, truncate: true)
timeToRead
fields {
slug
}
frontmatter {
date(formatString: "DD/MM/YYYY")
title
}
}
}
}
}
`

You may have other instances where you need to filter out scheduled posts, such as an RSS feed. Make sure to apply the filter in those cases, too.

Access during development

But what if you're like me and want to see your beautiful blog posts in action before letting it off into the wild? 🥺 Well, I got you covered.

By default, Gatsby supports 2 environments:

  • Development, when you run gatsby develop, and
  • Production, when you run either gatsby build or gatsby serve

And it's as simple as switching out your GraphQL query depending on the environment. One that includes a filter (i.e.productionQuery) and one that doesn't (i.e. developmentQuery).

JAVASCRIPT
// ... (gatsby-node.js)
const developmentQuery = `...`
const productionQuery = `...`
let result
if (process.env.NODE_ENV === 'production') {
result = await graphql(productionQuery)
} else {
result = await graphql(developmentQuery)
}
//...

Refactoring the code

Unfortunately, string interpolations do not work in graphql tags, except for those in a gatsby-__.js file.

If you're like me, you might opt for a more elegant solution. In a separate file, I declared the following:

JAVASCRIPT
exports.allBlogPostsQuery = (queryBody) => {
return process.env.NODE_ENV === 'production' ? getPublishedPosts(queryBody) : getAllPosts(queryBody)
}
const getAllPosts = (queryBody) => (`
{
allMdx(sort: { fields: [frontmatter___date], order: DESC }) {
${queryBody}
}
}
`)
const getPublishedPosts = (queryBody) => (`
{
allMdx(
sort: { fields: [frontmatter___date], order: DESC },
filter: {
fields: { isScheduledPost: { eq: false } }
}
) {
${queryBody}
}
}
`)

You may then go on and use it in your gatsby-__.js file.

JAVASCRIPT
const result = await graphql(
allBlogPostsQuery(`
edges {
node {
fields {
slug
}
frontmatter {
title
date
}
}
}
`)
)

WRITTEN BY

Code and stuff