# Render draft posts locally in Gatsby

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:

```js
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](https://www.gatsbyjs.com/docs/how-to/local-development/environment-variables/).

## Tell gatsby-source-filesystem to pull in the drafts

You’ve probably got something like this in your `gatsby-config.js`:

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

```js
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](https://www.gatsbyjs.com/plugins/gatsby-source-filesystem/?=gatsby-source-filesystem)
should work (below). To differentiate between the "posts" and the "drafts" you
filter in `allFile`. The instance name (posts|drafts) live in
`sourceInstanceName`:

```graphql
{
  allFile(filter: { sourceInstanceName: { eq: "drafts" } }) {
    edges {
      node {
        foo
        bar
      }
    }
  }
}
```

You can check that with GraphiQL query explorer tool at
`http://localhost:8000/___graphql`:

[Dig into the data with GraphiQL](https://stream.mux.com/NNRCgAnP28XPRkSJ3CaG5RWZkExeDKSlIe4zb3SGnZY.m3u8)

If you don’t run MDX, then you should be good with the data.

Note that, if you’re using **MDX** in a form of
[gatsby-plugin-mdx](https://gatsbyjs.com/plugins/gatsby-plugin-mdx), **you can’t
sub-select in the parent of a node**, and you don’t have
`filter.sourceInstanceName`, but you can add something similar yourself while
you’re creating the pages.

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:

```js
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:

```js {5,8-14,24}
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 `getNode` helper coming in from the
  `onCreate` hook.
- Then, make the `sourceName` field, after this you should have it available in
  `filter.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`:

```graphql {4}
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:

```js {8}
import React from 'react'
import NotFound from './404'

const Drafts = props => {
  return <span>Draft stuff..</span>
}

export default process.env.NODE_ENV !== 'development' ? NotFound : Drafts
```

This way the 404 page has the HTTP status code of 200, and not 404. This might
  be an issue in some cases.

Here’s more expanded example, rendering a link, title, and an excerpt:

```jsx {20}
// 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 : Drafts
```

## Add it to your nav

This site’s nav when running it locally, has the drafts menu item up there:

![A screenshot of a UI where one item is ”Drafts“](/images/drafts-in-the-nav.png)

Below is an un-styled example using the Gatsby’s Link navigation component:

```jsx
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:

```shell
$ gatsby build
$ gatsby serve
```

Test 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-filesystem` that drafts exist.
- Add the `name` field 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.
