I'm just getting started on a blog, and I wanted to use Gatsby for it.
In reading the docs for Gatsby, I came across MDX. MDX lets you write posts in Markdown while also letting you import and use JSX. Once it's set up, I get to write my markdown, add some flair with components when needed, import other MDX files, and toss in some Javascript if I need to set any variables or do any maps.
You may be thinking that sounds amazing. And you would be right. The hype definitely feels warranted. @johno and @silvenon did some awesome work.
Something I was having a bit of trouble with, though, was how to hide a draft on my live site while I was still working on it. The Gatsby plugin is awesome in that it will just find your mdx
files in src/pages
and create a page out of each of them for you, with only minimal setup. But with that magic, we limit our configuration options. There's not any great way to prevent it from skipping over the src/pages
posts that you've marked as a draft with the frontmatter.
Note: The probably better way to solve this problem would be to move the posts outside of
src/pages
so that the MDX plugin does not build anything automatically and then follow this guide to create the blog posts. With this setup, you would just filter out any posts that havepublished: false
out of yourcreatePages
query, and you'd be good to go.
But maybe you still want to use the default src/pages
magic but don't want to move the files or to worry about GraphQL or to jump into your gatsby-node
file. Maybe you just like all of your drafts being in the same directory that they'll be built in, thanksverymuch.
Well, as my high school algebra teacher said pretty much every day, there's more than one way to get to the mall. Indeed there is, Sister Phillips. If we don't follow the guide above, we can let the plugin just render the draft page. And then instead of showing the full post content, we can just render some custom 'this post isn't finished yet' messaging. Et voilà! Now the world won't find all our erotic fan fiction that we know the world isn't quite ready for.
Note: The below solution, while it works, is less important than hiding any links to unpublished drafts on our blog index page. If we're really trying to hide any drafts, better to hide the post entirely than show a 'not found' message. If we filter out the unpublished pages from our
allMdx
(allMdx(filter: {frontmatter: {published: {eq: true}}})
instead of justallMdx
) query, then it will be suuuuuper difficult for anyone to guess a post slug and find our racy novellas. BUT! Nothing wrong with a fallback in case someone happens upon the slug or we forget to filter out the drafts.
First, we'll install the gatsby plugin and its requirements:
yarn add gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react
Next, we add the MDX plugin to our gatsby-config
and tell it to wrap each post in a layout component. The layout component is where we can inject the styles that every blog post shares so that we don't have to remember to import all of our components in the top of each separate .mdx
file.
// gatsby-config.jsmodule.exports = {...,plugins: [...,{resolve: `gatsby-plugin-mdx`,options: {defaultLayouts: {default: require.resolve("./src/templates/post-layout.js"),}}}]}
MDXProvider
templateThis template is where we'll use the MDXProvider
, which can be used to customize our MDX posts. In the documentation, it describes how we can use custom components when rendering. The docs lists every component that MDX could render from markdown, so you can customize any of your headers, paragraphs, tables, thematic breaks, block quotes, etc, through the components
prop on the MDXProvider
.
import React from "react"import { MDXProvider } from "@mdx-js/react"import { Link } from "gatsby"import { CustomH1, CustomParagraph } from "../components/ui"const shortcodes = {Link,h1: CustomH1,p: CustomParagraph,...}export default ({ children }) => (<MDXProvider components={shortcodes}>{children}</MDXProvider>)
This setup would allow us to use Gatsby links anywhere in our MDX files without importing the Link
into each file. It also maps all h1 and p tags built from our markdown to some custom components.
But as the above stands, we'd still be posting our outlines and drafts for all to find. Again, they'll be pretty hidden once we filter out the unpublished pages from our allMdx
query for our blog index page. But assuming someone slips past our defenses and find a post we don't want the world to see yet, this file is the place to prevent the content from being rendered.
We'll put the published status in the frontmatter of each blog post, so we will have access to the published
boolean on pageContext.frontmatter.published
. And since we don't really want to hide the posts from ourselves locally while we're working on writing it, we'll add a check for the node environment and only hide the post text if it's on production
.
<!-- src/pages/posts/hidden.md -->---title: "hello, world"isPublished: falsedate: "02-23-2020"---# I hope the world can't find this post just yetAnd here is the rest of the content...
// src/templates/post-layout.jsimport React from "react"import { MDXProvider } from "@mdx-js/react"import { Link } from "gatsby"import { CustomHeader } from "../components/ui"const shortcodes = { Link, h1: CustomHeader }const NotFinishedMessage = () => <p>This post is still a work in progress. Send me a note to tell me to hurry up and finish it!</p>;const isProduction = process.env.NODE_ENV === 'production'export default ({ children, pageContext }) => (<MDXProvider components={shortcodes}>{isProduction && !pageContext.frontmatter.published ? (<NotFinishedMessage />) : (children)}</MDXProvider>)
And there we have it. If somehow someone makes it to an unfinished post in production, they'll see a message to keep their sneaky eyes outta here. Otherwise, like when we're developing, the full content will be shown.
Again, this extra guard is pretty small potatoes, since somewhere on the order of definitely zero people are typing random urls on my site to find unfinished posts. But now that you know you've got access to pageContext.frontmatter
, you could do something else in here. Instead of hide your drafts, you could format the published date or add the blog title or a list of tags here so that they work auto-magically for every blog post.
Thanks for reading along!