Building composable themes requires a slightly different mindset than building a conventional site. Consider the following cases with one Gatsby theme and one site.
Our site is built as usual, with whatever tech we prefer, etc. We also specify a theme as such:
__experimentalThemes: ["gatsby-theme-mystuff"];
A typical query on a site can look like this. Note that
there's nothing inherently wrong with this query, it sorts
by the publish date, filters out draft posts. When using the
same query in a site however, there's one main aspect of
this query that becomes problematic quickly: allMdx
.
{allMdx(sort: { fields: [frontmatter___date], order: DESC }filter: { frontmatter: { draft: { ne: true } } }) { ... }}
To understand why allMdx
is problematic in a themes
setting, consider the scenario where you have blog posts in
content/posts
and pages that you've written using MDX in
src/pages
. The blog posts need to be pulled into the
Gatsby node system using gatsby-source-filesystem
and to
add a table of contents to our src/pages
pages, we put
another gatsby-source-filesystem
on src/pages
. So we end
up with a config like this in our gatsby-config.js
.
module.exports = {plugins: [{resolve: `gatsby-source-filesystem`,options: {path: `content/posts`,name: `posts`}},{resolve: `gatsby-source-filesystem`,options: {path: `src/pages`}}]};
So the query we've written collects all of the Mdx
nodes
across both filesystem sources, which is not likely to be
what we want. The problem gets worse when you introduce
plugins like
gatsby-transformer-react-docgen,
which generate Mdx nodes into the Gatsby system. This causes
even more content to match our query.
To fix this, we can take a variety of different paths. One
is to change the query to an allFile
query. Functionally,
this is a similar query as our original allMdx
query. The
main differences are that we end up fetching drafts as well
and we have to programmatically sort after we fetch as well.
allFile(filter: { sourceInstanceName: { eq: "posts" } }) {edges {node {childMdx {...}}}}
One unfortunate consequence of this is that it locks us into using files as our data source. If someone is using a headless CMS then they can't use our theme with this query because we are now querying the source of our content instead of the format.
We can solve this problem by adding a new field to our
content: isNote
. This is the ultimate in flexibility
because it allows us to define whatever we want as the
criteria for what constitutes a Note.
exports.onCreateNode = ({ node, actions, getNode }) => {const { createNodeField } = actions;if (node.internal.type === `Mdx`) {const parent = getNode(node.parent);if (parent.internal.type === "File" &&parent.internalSourceName === "posts") {createNodeField({name: `isNote`,node,value: true});}}};
Then if we have a title
field in our Mdx
files, we can
filter the result set:
{allMdx(filter: { fields: { isNote: { eq: true } } }) {nodes {frontmatter {title}}}}
This is very flexible in that it allows us to access every
field as we're used to on Mdx
or any other type. It is
restricting in that we can't combine multiple types to
provide notes. To do that, we'll have to look in to
Constructing Query Types in Themes.