70 %
Chris Biscardi

Manipulating Gatsby Page Context with onCreatePage

Adding additional context to a page is a supported part of calling createPage. This field is often used to pass a simple identifying piece of information, like an id or a slug, that can be used by a page template to fetch the relevant information.

JS
createPage({
// Path for this page — required
path: `${edge.node.frontmatter.slug}`,
component: blogPostTemplate,
context: {
// Add optional context data to be inserted
// as props into the page component..
//
// The context data can also be used as
// arguments to the page GraphQL query.
//
// The page "path" is always available as a GraphQL
// argument.
}
});

However, when dealing with pages generated from src/pages, we don't have manual control of how the pages are constructed: gatsby-plugin-page-creator handles it for us. In gatsby-mdx we added support for generating pages from .mdx files by default.

Through adding support for .mdx pages by default, we also want to expose frontmatter defined in .mdx files to the page at runtime for use in rendering. To do that, we need to hijack the page and modify the page context to add the frontmatter.

In this case, we use the onCreatePage lifecycle which hands us every page that is created by Gatsby right after it's created. If the extension is .mdx, we do some processing and then deep merge the frontmatter in with the additional context so as to allow any pre-existing context to still be passed through.

JS
const path = require("path");
const fs = require("fs-extra");
const merge = require("lodash/merge");
const defaultOptions = require("../utils/default-options");
const extractExports = require("../utils/extract-exports");
const mdx = require("../utils/mdx");
module.exports = async (
{ page, actions },
pluginOptions
) => {
const { createPage, deletePage } = actions;
const { extensions, ...options } = defaultOptions(
pluginOptions
);
const ext = path.extname(page.component);
// we test to see if frontmatter is created already because that's what
// we're trying to insert and if we don't check we can end up in infinite loops
if (
extensions.includes(ext) &&
!page.context.frontmatter
) {
const content = await fs.readFile(
page.component,
"utf8"
);
const code = await mdx(content, options);
// grab the exported frontmatter
const { frontmatter } = extractExports(code);
deletePage(page);
createPage(
merge(
{
context: {
frontmatter: {
...frontmatter
}
}
},
page
)
);
}
};

Another interesting note is that we check to see if the frontmatter is already set in the page context. This is to prevent infinite loops that will occur if another plugin or user re-creates the page.