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 😋.
---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:
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
.
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:
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
orgatsby 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
).
// ... (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:
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.
const result = await graphql( allBlogPostsQuery(` edges { node { fields { slug } frontmatter { title date } } } `))