Programmatically Creating Pages

Sometimes you want to be able to programmatically access data from files in src/pages or create pages using MDX content lives at arbitrary locations outside of src/pages/ or in remote CMSs.

For instance, let's say you have a Gatsby website, and you want to add support for MDX so you can start your blog. The posts will live in content/posts/.

You can do this with the help of gatsby-source-filesystem, and some calls to createPages in gatsby-node.js. Let's dive in.

Sections

  1. Install Dependencies
  2. Edit Your gatsby-config.js
  3. Add MDX Files
  4. Generate Slugs
  5. Query For MDX Files
  6. Create Pages From Your Query
  7. Make A Template For Your Blog Posts
  8. Bonus: Make a Blog Index

1. Install Dependencies

If you haven't installed gatsby-mdx, do that now. You will also need to install gatsby-source-filesystem.

To install everything, from the root of your Gatsby project run:

npm install gatsby-mdx @mdx-js/tag @mdx-js/mdx gatsby-source-filesystem

2. Edit Your gatsby-config.js

To let Gatsby know that you'll be working with MDX content you need to add gatsby-mdx to the plugins array in your gatsby-config.js file.

note: gatsby-mdx uses .mdx by default as a file extension to recognize which files to use. You can also use .md as a file extension if you want.

/* gatsby-config.js */
module.exports = {
plugins: [
/* 1) this adds support for *.mdx files in gatsby */
"gatsby-mdx",
/*
* 2) this adds a collection called "posts"
* that looks for files in `content/posts/`
*/
{
resolve: "gatsby-source-filesystem",
options: {
name: "posts",
path: `${__dirname}/content/posts/`
}
}
]
};

Here you're telling Gatsby "Look in a folder called content/posts that's located in the root of my project. Make any file inside accessible through a GraphQL query and set the sourceInstanceName property on the file nodes to posts"

You can read about gatsby-source-filesystem if you'd like to learn more.

3. Add MDX Files

Before you can write any GraphQL queries, and programmatically create pages, you need to add some content.

Make a folder called content/posts and create two files in it called blog-1.mdx and blog-2.mdx. You can do this on the command line in a terminal by using the following commands from the root of your project.

mkdir -p path/to/a/directory will create every folder in the path if it does not exist.

touch <filename> will create an empty file named <filename>. The brackets ({) are an expansion which means we can create multiple files in one command.

mkdir -p content/posts
touch content/posts/blog-{1,2}.mdx

Open up each of the files you just created and add some content.

---
title: "Blog Post 1"
---
Trying out MDX
---
title: "Blog Post 2"
---
Gatsby is the best


4. Generate Slugs

Because we're programmatically creating pages, we need to tell Gatsby which page should be at which URL. We'll do that using another API named a nodeField, but if you want to set the URLs in your frontmatter, you can skip this step.

// in gatsby-node.js
const { createFilePath } = require("gatsby-source-filesystem");
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
// We only want to operate on `Mdx` nodes. If we had content from a
// remote CMS we could also check to see if the parent node was a
// `File` node here
if (node.internal.type === "Mdx") {
const value = createFilePath({ node, getNode });
createNodeField({
// 1) this is the name of the field you are adding,
name: "slug",
// 2) this node refers to each individual MDX
node,
value: `/blog${value}`
});
}
};

The value in the createNodeField call is the URL we'll use later to set up our page. /blog${value} is a template string that will result in:

  • blog-1.mdx => "localhost:8000/blog/blog-1/"
  • blog-2.mdx => "localhost:8000/blog/blog-2/"

createFilePath is a function from gatsby-source-filesystem that translates file paths to usable URLs.

onCreateNode is a Gatsby lifecycle method that gets called whenever a new node is created. In this case we make sure we only touch Mdx nodes.

5. Query For MDX Files

Now we need to write a GraphQL query that we can use to create a set of pages. Start up Gatsby in development mode, and open up GraphiQL. In the root of your Gatsby project run:

gatsby develop

Then, in your browser open up https://localhost:8000/___graphql and paste the following into GraphiQL:

query {
allMdx {
edges {
node {
id
fields {
# this is the slug field you created in the last section
slug
}
}
}
}
}

This is the basic query you need to create your mdx pages, but you can edit and add to this to fit your needs. If you skipped the last step and want to use frontmatter for your slugs instead of the generated field, replace fields with frontmatter.

6. Create Pages From Your Query

We'll use the query we just saw working in GraphiQL to query Gatsby's node system and create a set of pages from those nodes. The Gatsby API we need to use is called createPages.

// in gatsby-node.js
const path = require("path");
exports.createPages = ({ graphql, actions }) => {
// Destructure the createPage function from the actions object
const { createPage } = actions;
return new Promise((resolve, reject) => {
resolve(
graphql(
`
{
allMdx {
edges {
node {
id
fields {
slug
}
}
}
}
}
`
).then(result => {
// this is some boilerlate to handle errors
if (result.errors) {
console.error(result.errors);
reject(result.errors);
}
// We'll call `createPage` for each result
result.data.allMdx.edges.forEach(({ node }) => {
createPage({
// This is the slug we created before
// (or `node.frontmatter.slug`)
path: node.fields.slug,
// This component will wrap our MDX content
component: path.resolve(`./src/components/posts-page-layout.js`),
// We can use the values in this context in
// our page layout component
context: { id: node.id }
});
});
})
);
});
};

7. Make A Template For Your Blog Posts

Make a file called posts-page-layout.js in src/components. We need to use a special component from gatsby-mdx called MDXRenderer to render any programmatically accessd MDX content.

// src/components/posts-page-layout.js
import React from "react";
import { graphql } from "gatsby";
import MDXRenderer from "gatsby-mdx/mdx-renderer";
function PageTemplate({ data: { mdx } }) {
return (
<div>
<h1>{mdx.frontmatter.title}</h1>
<MDXRenderer>{mdx.code.body}</MDXRenderer>
</div>
);
}
/*
*
* `id` is passed in through the `context` object from `createPage`
* GraphQL requires us to declare the type of arguments at the top
* of the query before using them in the query body. We don't need to
* worry about finding the `id` value from the props and passing it in
* because we put it in `context` so Gatsby handles that for us.
*
*/
export const pageQuery = graphql`
query BlogPostQuery($id: String) {
mdx(id: { eq: $id }) {
id
frontmatter {
title
}
code {
body
}
}
}
`;

That's it, you're done. Run gatsby develop and enjoy your new MDX powers.

Now you have all the pieces you need to programmatically create pages with Gatsby and gatsby-mdx. Check out our other guides to find out more about all of the cool stuff you can do with gatsby-mdx.

8. Bonus: Make a Blog Index

// src/pages/index.js
import React from 'react'
import { Link, graphql } from 'gatsby'
const BlogIndex = ({ data }) => {
const { edges: posts } = data.allMdx
return (
<div>
<h1>Awesome MDX Blog</h1>
<ul>
{posts.map(({ node: post }) => (
<li key={post.id}>
<Link to={post.fields.slug}>
<h2>{post.frontmatter.title}</h2>
</Link>
<p>{post.excerpt}</p>
</li>
))
</ul>
</div>
)
}
export const pageQuery = graphql`
query blogIndex {
allMdx {
edges {
node {
id
excerpt
frontmatter {
title
}
fields {
slug
}
}
}
}
}
`
export default BlogIndex
edit this page on GitHub