⚠️ This lesson is retired and might contain outdated information.

Build Gatsby Page Slugs Dynamically from Markdown with gatsby-node.js

Taylor Bell
InstructorTaylor Bell
Share this video with your friends

Social Share Links

Send Tweet
Published 6 years ago
Updated 2 years ago

Gatsby includes several Node APIs for building sites. In this lesson, we’ll use the createPages API to dynamically build a page for each of our posts, by using the URL path in the frontmatter of each Markdown doc found via a GraphQL query.

Instructor: [00:00] We have our post title showing, but they're not clickable. Let's go ahead and make them links. To do that, we're going to import link from Gatsby. We can come down here to our frontmatter.title. We can surround this with link and our to prop will be frontmatter.path and save this.

[00:17] When this reloads, we can see that they're links now. When we click it, we get a 404 page. That's because we don't actually have a URL built for our post. In order for Gatsby to create the pages that we need for each blog post, we're going to create a file gatsby-node.js inside of the root of the directory.

[00:37] Inside of our source directory, we're going to create a templates directory and we will create a new file source/templates/blogpost.js. On the left hand side, I have gatsby-node.js, and on the right hand side, I have blogpost.js. Let's just knock out the blog post template first.

[01:03] We'll import React. We'll bring in GraphQL from Gatsby. Now, we'll just create a new constant template. We'll bring in props for now. Just to keep it simple, we'll do a return of just a div with blog post here.

[01:19] We'll export template by default and that will be good enough for now. There are several API's that Gatsby gives us access to. In order to create pages, we will use the aptly named create pages API. To get started, we'll create a new export function called createPages.

[01:35] We're going to destructure GraphQL for finding our files and actions which is where createPage lives. Now, we'll destructure createPage from our actions. Our createPages function will return a new promise due to the async nature of file creation.

[01:51] We'll do return new promise, which will have a resolve and a reject. In order to create the page, we're going to need access to our blog post template. We'll create a new variable for it. Since it's on the file system, we'll use path.resolve and send it to our source directory /templates/blogpost. We'll require path at the top of our file.

[02:15] Now we'll resolve the promise with a call to GraphQL, and we'll pass in our query for allMarkdownRemark.edges node frontmatter, and the path where we want our posts to live at.

[02:27] Now we'll add a then where we pass the result into a function, and result contains a data object with a shape that matches our query. We'll look at result.data.allMarkdownRemark.edges, and remember the edges are a path to the file system node.

[02:45] With this in mind, we're going to do a for each. Move this over to give us a little bit more room here. For each of our edges, we're going to extract the path from the node's frontmatter. We can destructure the node. We know that our path will be at node.frontmatter.path.

[03:05] Now, we can call the createPage action. The first parameter is a path for the page URL and will be the component to render, so in this case, the blog post template. The third parameter is the context object that will make its way into our blog post template component as a prop.

[03:22] We want our template to know what the path to our file is. We'll call it path slug, since path is a reserved keyword, and the value of path is what is supplied in our node frontmatter.

[03:33] After our call to createPage, we'll resolve that out of the promise. Since we have updated gatsby-node.js, we need to restart gatsby develop, so we run that. We get an error, "Cannot read property allMarkdownRemark of undefined."

[03:48] This would be on line 23. It's happening with result.data. There's something wrong with the query. Let's jump back over to our GraphiQL browser. It looks like the root query type. We need to add query.

[04:06] When we click into here, this is where allMarkdownRemark and stuff is. We just need to wrap this with query. Let's rerun gatsby develop, refresh the page, click, and we now have blog post here matching our blog post template.

Royston Shufflebotham
Royston Shufflebotham
~ 6 years ago

I don't think the second resolve() call added at 03:35, to line 33, makes sense. The promise was already resolve()d on line 9, and the code around line 33 is already running in a .then() block chained from that promise.

yerlanyr
yerlanyr
~ 5 years ago

You don't have to create new Promise, because the graphql function itself returns a Promise. You could just write like:

return graphql(`....`).then(result => {...});
Tony Catalfo
Tony Catalfo
~ 5 years ago

const path = require('path') Why are we using require?

Tony Catalfo
Tony Catalfo
~ 5 years ago

Why is blogPostTemplate a component? When I hover over it I get const blogPostTemplate: string?

Edmundo Ruiz
Edmundo Ruiz
~ 5 years ago

There are a number of errors in the code snippets in the transcript:

  1. The graphql node string is not closed

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

  return new Promise((resolve, reject)) => {
    const blogPostTamplate = path.resolve('src/templates/blogPost.js') 
    
  })
})```
Instead of ```
const path = require('path')

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

  return new Promise((resolve, reject)) => {
    const blogPostTemplate = path.resolve('src/templates/blogPost.js') 
    
  })
})```
Zone Chen
Zone Chen
~ 5 years ago

const path = require('path') Why are we using require?

Because this gatsby-node.js will be executed by nodeJs runtime.

Zone Chen
Zone Chen
~ 5 years ago

I found another question: Why we require src/templates/blogPosts.js in gatsby-node.js.

Since these two js file use different module system.

  • src/templates/blogPosts.js: es module, and (should be) compiled into build.js something like that.
  • gatsby-node.js will be executed during the runtime.

Just don't get it, how can they work together ?

Cristian Gabbanini
Cristian Gabbanini
~ 5 years ago

I had a few problems with the code on this page (gatsby was throwing a 404 error for my blog posts and I wasn't able to know why..). Anyway... reading the following page from the gatsby docs really helped me: https://www.gatsbyjs.org/docs/adding-markdown-pages/

Jon Saints
Jon Saints
~ 5 years ago

I don't think the second resolve() call added at 03:35, to line 33, makes sense. The promise was already resolve()d on line 9, and the code around line 33 is already running in a .then() block chained from that promise.

This is correct. You just need to return after mapping over the edges. do not try to resolve inside of the map function that will not work.

~ 4 years ago

Just to flag this again - the code shown in both the transcript and the screencast doesn't work & needs to be fixed.

As others have noted, there's no need to instantiate a new promise at all. The graphql call returns a promise, so we can just return that directly.

const path = require('path')

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve('src/templates/post.js')
  return graphql(
    `
      query {
        allMarkdownRemark {
          edges {
            node {
              frontmatter {
                path
              }
            }
          }
        }
      }
    `
  ).then(result => {
    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
      const { path } = node.frontmatter
      return createPage({
        path,
        component: blogPostTemplate,
        context: {
          pathSlug: path,
        },
      })
    })
  })
}
Marcell Ciszek
Marcell Ciszek
~ 4 years ago

Is it possible to fetch *.tsx files in gatsby-node.js ? :)

Lauro Silva
Lauro Silva
~ 4 years ago

Hey Marcell - make sure you the lasted Gatsby version. Gatsby natively supports JavaScript and TypeScript, you can change files from .js to .tsx at any point to start adding types and gaining the benefits of a type system. If you need more information, check out this document outline Typescript support.

Markdown supported.
Become a member to join the discussionEnroll Today