Gatsby Starter Kit

a set of starters for Gatsby.js

Markdown files' names conventions

All starters of Gatsby Starter Kit use Markdown files to store content. The markdown files are gathered in sub-folders of src/content/. Depending on a starter there could be four type sub-folders under src/content/:

root
  └── src
      ├── content
      │   ├── docs
      │   ├── pages
      │   ├── posts
      │   └── parts

To effectively employ the Kit’s built-in scripts you should obey some naming rules.

Posts

Let’s start with posts. If you install the Blog starter with a default content you will see something like this.

root
  └── src
      ├── content
      │   ├── posts
      │   │   ├── 2017-10-01___two-things-are-infinit
      │   │   |   ├── photo.png
      │   │   │   └── index.md
      │   │   ├── 2017-10-03___be-who-you-are
      │   │   |   ├── photo.png
      │   │   │   └── index.md
      |   |
      |   |   ...
      |   |
      │   │   └── 2017-10-21___like-nonsense
      │   │       ├── photo.png
      │   │       └── index.md

Every post markdown file index.md has its own separate folder. The name of the post’s folder should be consistent with the pattern.

  • a post date prefix (YYYY-MM-DD pattern)
  • a separator ___ (three underscores)
  • a slug (kebab-case or whatever you want)

The starters use the onCreateNode method of the Gatsby Node API to transform the name of every folder into two node fields slug and prefix.

slug

Take a look at the code of the gatsby-node.js file.

const { createFilePath } = require(`gatsby-source-filesystem`);

const SLUG_SEPARATOR = '___';

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions;
  if (node.internal.type === `MarkdownRemark`) {
    const fileNode = getNode(node.parent);
    const filePath = createFilePath({ node, getNode });

    const source = fileNode.sourceInstanceName;

    const separatorExists = ~filePath.indexOf(SLUG_SEPARATOR);

    let slug;
    let prefix;

    if (separatorExists) {
      const separatorPosition = filePath.indexOf(SLUG_SEPARATOR);
      const slugBeginning = separatorPosition + SLUG_SEPARATOR.length;
      slug =
        separatorPosition === 1
          ? null
          : `/${filePath.substring(slugBeginning)}`;
      prefix = filePath.substring(1, separatorPosition);
    } else {
      slug = filePath;
      prefix = '';
    }

    if (source !== 'parts') {
      createNodeField({
        node,
        name: `slug`,
        value: slug,
      });
    }
    createNodeField({
      node,
      name: `prefix`,
      value: prefix,
    });
  }
};

Pay attention to two fragments. The first assigns null to slug when the separator is at the beginning of the folder’s name.

slug = separatorPosition === 1 ? null : `/${filePath.substring(slugBeginning)}`;

So, if you name a folder like below, with separator ___, but without the date prefix, the slug will have value of null.

src/content/posts/___this-is-post-folder-name/

The second fragment prevents creation of the slug field for nodes with source equal to parts.

if (source !== 'parts') {
  createNodeField({
    node,
    name: `slug`,
    value: slug,
  });
}

Later in the same file we use the onCreatePage method of the Gatsby Node API to create web pages for every eligible node.

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;

  return new Promise((resolve, reject) => {
    const pageTemplate = path.resolve('./src/templates/PageTemplate.js');

    resolve(
      graphql(`
        {
          allMarkdownRemark(filter: { fields: { slug: { ne: null } } }) {
            edges {
              node {
                fields {
                  slug
                }
              }
            }
          }
        }
      `).then(result => {
        if (result.errors) {
          console.log(result.errors);
          reject(result.errors);
        }

        const items = result.data.allMarkdownRemark.edges;

        items.forEach(({ node }) => {
          const slug = node.fields.slug;
          const source = node.fields.source;

          createPage({
            path: slug,
            component: pageTemplate,
            context: {
              slug,
            },
          });
        });
      })
    );
  });
};

This time pay attention to this fragment with GraphQL filter

allMarkdownRemark(filter: { fields: { slug: { ne: null } } }) {

We query only for nodes with the slug value different than null. So, this combined with the fragments described earlier make that web pages are not created for any of parts markdown files and posts or pages named like this /src/content/pages/___post-folder-name.

Thus, if you want to create a draft post or page, name it’s folder with the separator at the beginning.

src/content/posts/___hey-iam-a-draft-post/

Whenever you will be ready to publish the post, just edit it’s folder name, add the date prefix.

src/content/posts/2018-08-19___hey-iam-a-published-post-now/

prefix

The prefix field is used by the starters to order posts or pages. Take a look at the code below.

export const query = graphql`
  query {
    posts: allMarkdownRemark(
      filter: { fields: { source: { eq: "posts" }, slug: { ne: null } } }
      sort: { fields: [fields___prefix], order: DESC }
    ) {
      edges {
        node {
          excerpt(pruneLength: 250)
          fields {
            slug
            prefix
          }
          frontmatter {
            title
            categories
          }
        }
      }
    }
  }
`;

It’s a fragment of the src/pages/blog.js file of the Blog starter. It presents a GraphQL query for data for a component presenting a list of blog posts. Pay attention to the query arguments.

filter: { fields: { source: { eq: "posts" }, slug: { ne: null } } }
sort: { fields: [fields___prefix], order: DESC }

We query for posts which have the slug field defined, different than null and we order the result according to the prefix field.

So, if you name a post like below

src/content/posts/hey-iam-a-unordered-post/

the script will not know where to place the post on the list.

Pages

The pages are the subjects to the same naming conventions.

  • A name with the separator at the beginning makes a draft page.
  • The prefix orders pages.

A note. The docs markdown files in the Classy Docs starter are standard pages, they got a separate folder only for convenience.

Motivation

These naming conventions let you at a single glance learn about the posts/pages status or position.

You do not have to open the post file to know if the post is still a draft or a published one already. You do not have to open the page file to know if it should be before or after another one on the listing. That’s convenient, isn’t it?