This post looks how to render posts from your drafts directory in an MDX Gatsby setup. Locally only, so you don’t leak unfinished posts.
I’ve got this habit of using my drafts directory contents/drafts as a kind of
a scratch pad. I jot down ideas, create post stems, and throw in some
interesting links as I do research. I’ve got ten or so posts cooking at all
times.
But this is slightly janky, if I want to preview the post I have to move it into
the content/posts directory so it’s rendered by Gatsby. And I got to thinking,
that what if I render the drafts too? Making sure not to deploy them or any
of the related routes.
Only on development
You can get the environment from the Gatsby environmental variables:
const isDevelopment = process.env.NODE_ENV === 'development'Note that line, we need it later on.
You can read more about the environmental variables from Gatsby docs.
Tell gatsby-source-filesystem to pull in the drafts
You’ve probably got something like this in your gatsby-config.js:
module.exports = {
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'posts',
path: path.resolve('./contents/posts'),
},
},
],
}gatsby-source-filesystem can be called more than once, so we can add another
one for the drafts, but only on the development environment. Set up a little
ternary condition:
const isDevelopment = process.env.NODE_ENV === 'development'
module.exports = {
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'posts',
path: path.resolve('./contents/posts'),
},
},
...(isDevelopment
? [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'drafts',
path: path.resolve('./contents/drafts'),
},
},
]
: []),
],
}Now we’ve told Gatsby that drafts exist.
Query the data
Writing the GraphQL queries for the drafts data isn’t that surprising, but there are few things to consider if you’re running MDX.
Data handling in non-MDX setup
If you’re a non-MDX user, then the
code example in gatsby-filesystem docs
should work (below). To differentiate between the "posts" and the "drafts" you
filter in allFile. The instance name (posts|drafts) live in
sourceInstanceName:
{
allFile(filter: { sourceInstanceName: { eq: "drafts" } }) {
edges {
node {
foo
bar
}
}
}
}You can check that with GraphiQL query explorer tool at
http://localhost:8000/___graphql:
If you don’t run MDX, then you should be good with the data.
Next, let's make it filterable.
Make the data filterable by source name
You can add arbitrary data under the fields object in the GraphQL data, by using
the createNodeField helper inside the onCreate hook. You might have
something like this your gatsby-node.js file:
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === 'Mdx') {
createNodeField({
name: 'slug',
node,
value: createFilePath({
node,
getNode,
trailingSlash: false,
}),
})
}
}The above code creates the slug field for the posts. But we want to add an extra
field: filter.fields.sourceName. We can do that with the same
createNodeField helper:
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === 'Mdx') {
const parent = getNode(node.parent)
const isDraft = parent.sourceInstanceName === 'drafts'
if (parent.internal.type === 'File') {
createNodeField({
name: 'sourceName',
node,
value: parent.sourceInstanceName,
})
}
const slug = createFilePath({
node,
getNode,
basePath: 'posts',
trailingSlash: false,
})
// Check if it’s a draft or a post.
const entryPath = path.join(isDraft ? 'draft' : '', slug)
// Make the slug field.
createNodeField({ name: 'slug', node, value: entryPath })
}
}The highlighted bits explained:
- First, get the parent, using the
getNodehelper coming in from theonCreatehook. - Then, make the
sourceNamefield, after this you should have it available infilter.fields.sourceName, check from GraphiQL. - Finally, generate the correct path for the entry, I’d want the draft posts to
be available from the path
/draft/draft-name.
Data handling with MDX
This is pretty much the full query that I use, it’s filtered by sourceName:
query ($skip: Int, $limit: Int = 10, $sourceName: String = "drafts") {
allMdx(
# Show only the drafts
filter: { fields: { sourceName: { eq: $sourceName } } }
# Sort the drafts by date
sort: { fields: [frontmatter___date], order: DESC }
# Skip and limit are for pagination
skip: $skip
limit: $limit
) {
edges {
node {
id
frontmatter {
title
date(formatString: "DD MMMM, YYYY")
}
fields {
slug
}
excerpt
}
}
}
}Now, put that into a template.
Render a list of drafts
Whenever rendering content, it needs to be at least two places:
/draft/<draft-name>: the individual post page, in my case it’s handled by the same template that renders posts. So that’s already done./drafts: a list of all the items, which is the archive page.
Let’s do the archive page now.
If you put anything in src/pages it becomes a route. But you also want to
make sure that the drafts route is not rendered anywhere else but in the local
environment. We can import the 404 page, and then export the 404 page if we’re
not on development, otherwise we’ll just render the drafts template:
import React from 'react'
import NotFound from './404'
const Drafts = props => {
return <span>Draft stuff..</span>
}
export default process.env.NODE_ENV !== 'development' ? NotFound : DraftsHere’s more expanded example, rendering a link, title, and an excerpt:
// src/pages/drafts.js
import React from 'react'
import NotFound from './404'
const Drafts = props => {
const { edges } = props.data.allMdx
return (
<div>
{edges.map(draft => {
<Link to={draft.node.fields.slug}>
<h2>{draft.node.frontmatter.title}</h2>
</Link>
<p>{draft.node.frontmatter.excerpt}</p>
})}
</div>
)
}
export default process.env.NODE_ENV !== 'development' ? NotFound : DraftsAdd it to your nav
This site’s nav when running it locally, has the drafts menu item up there:

Below is an un-styled example using the Gatsby’s Link navigation component:
import { Link } from 'gatsby'
return (
<nav>
{process.env.NODE_ENV === 'development' && (
<Link activeStyle={{ borderBottom: '2px solid black' }} to='/drafts'>
Drafts
</Link>
)}
{/* More nav items etc... */}
</nav>
)The activeStyle prop is a feature of reach-router (which Gatsby uses under the
hood). It knows the current path, and it has the to prop, so it can do a
little check if the nav item is the current page.
Test it up
Build the project, then serve the project:
$ gatsby build
$ gatsby serveTest that the single draft /draft/my-post-draft route and the /drafts aren’t
accessible.
Also check your projects public directory that there aren’t any
component---src-draft-*.js templates there, and that the
public/drafts/index.html is empty.
Conclusions
To sum this post up:
- Tell
gatsby-source-filesystemthat drafts exist. - Add the
namefield to the filter, so the MDX data can be filtered. - Query the data related to the drafts with GraphQL.
- Make the drafts template to render the drafts archive page.
- Add drafts to the menu.
Thanks for reading. Hope this was helpful.