Chapter 10. Gatsby Themes

In the previous chapter, we looked at two ways to extend Gatsby functionality with plugins and starters. But these aren’t the only means to extend Gatsby sites. Themes are a subset of Gatsby plugins that provide additional functionality through a distinct Gatsby configuration file. Because themes can encompass not only aesthetic differences but also additions to features like data sourcing and interface code, the name is a bit of a misnomer. One useful way to think of a Gatsby theme is as a Gatsby site that has its own configuration and can be installed into another Gatsby site.

In this chapter, we’ll first look at themes in the context of the larger Gatsby ecosystem by comparing them to other ecosystem elements like plugins and starters. Then, we’ll cover how to use a Gatsby theme in an existing site before directing our attention to how to create Gatsby themes from scratch. Finally, we’ll end this chapter with an examination of two concepts that make themes in Gatsby unique: shadowing and composition.

Gatsby Themes in Context

A theme in Gatsby is a special kind of plugin that contains its own gatsby-config.js and is responsible for providing prebuilt functionality, data sourced from external sources, and user interface code. To create a Gatsby theme, all of the baseline default configuration (shared functionality, data sourcing, design elements, etc.) is abstracted out of a Gatsby site and converted into an installable package.

Since they behave just like other Gatsby plugins, themes can be provided through a registry (such as NPM), and other Gatsby sites that leverage a theme can manage it as just another dependency in the package.json file. While these characteristics distinguish themes from plugins and starters, it can sometimes be difficult for Gatsby developers to determine when it’s appropriate to use a plugin, starter, or theme. Let’s take a closer look at the differences.

Differences from Plugins and Starters

In terms of maintenance, plugins and themes differ considerably from starters in that they can be released and updated as normal packages. Because starters are often immediately updated and diverge considerably from the source once installed, it’s much more challenging to synchronize changes once they’ve begun to deviate in development. In fact, this limitation of starters was one of the primary motivators for offering themes in the first place. As such, themes are more flexible than starters. But what are the similarities and differences between plugins and themes?

When it comes to configuration, plugins and themes are similar in that both provide configuration options so that their consumers can apply arbitrary settings to them, to an extent much more powerful than with starters (which largely only allow you to set the name of the directory into which they will be installed and are only configurable through direct modification of code). Plugins and themes both enable shadowing, which allows Gatsby sites that use them to override a specific file with their own homegrown version of it, thus offering different behavior.

The most important difference between a plugin and a theme is that whereas a plugin is leveraged on its own in a Gatsby site, themes are capable of abstracting the functionality provided by multiple plugins into a single theme. In short, themes provide collections of plugins in addition to custom components. This means that you can install a single theme to access functionality across multiple plugins, whereas without themes, you would need to install plugins individually. While custom components in plugins are intended for use as is in a Gatsby site, custom components in themes are intended for use through shadowing, so that their functionality can be overridden and customized.

Note

The Gatsby documentation contains a page that comprehensively accounts for the differences between plugins, starters, and themes and a flowchart for developers choosing between them.

Deciding Between Using and Creating a Theme

Before we turn to the actual usage and creation of themes, it’s important to be able to make an educated decision about whether to leverage a preexisting theme that exists in the wider Gatsby ecosystem or to create a new theme from scratch. That decision making should include the following considerations:

  • Consider using an existing theme if:

    • You already have an existing Gatsby site with considerable divergence from any available Gatsby starter, making it impossible to restore such a state.

    • You wish to rely on the theme’s versioning and release management instead of rolling your own.

    • You plan to leverage multiple features on your site, but no starter exists in the wild with the right blend of functionality (use multiple themes instead).

  • Consider creating a new theme if:

    • You plan to reuse similar features as a bundle of functionality repeated across and depended on by multiple Gatsby sites.

    • You wish to share your new Gatsby functionality with others.

Using Gatsby Themes

If you’ve made the decision to use an existing Gatsby theme rather than creating one from scratch, you have a few options. You can either create a new site based on a theme, use a theme in an existing Gatsby site you’ve already implemented, or use multiple Gatsby themes in that site. We’ll cover each of these approaches in turn in this section.

Starting a New Site from a Theme

The fastest way to use an existing theme to spin up a new Gatsby site is to leverage a starter that is associated with the theme. For example, gatsby-starter-blog-theme, a Gatsby starter, is a theme starter for the gatsby-theme-blog theme. As opposed to a regular Gatsby starter, which we’ve seen multiple times throughout this book, a theme starter creates a new site that both installs and configures one or multiple Gatsby themes.

Creating a new Gatsby site based on a Gatsby theme starter involves precisely the same process as installing any other starter. For instance, to install the Gatsby blog theme starter, execute the following command:

$ gatsby new gtdg-ch10-using-themes gatsbyjs/gatsby-starter-blog-theme

Let’s take a quick look at what happens inside the site after executing this command. First, as with any Gatsby site created from a starter, you’ll see that some information is already prepopulated for you in the Gatsby configuration file:

module.exports = {
  plugins: [
    {
      resolve: "gatsby-theme-blog",
      options: {},
    },
  ],
  // Customize your site metadata:
  siteMetadata: {
    title: "My Blog Title",
    author: "My Name",
    description: "My site description...",
    siteUrl: "https://www.gatsbyjs.com/",
    social: [
      {
        name: "twitter",
        url: "https://twitter.com/gatsbyjs",
      },
      {
        name: "github",
        url: "https://github.com/gatsbyjs",
      },
    ],
  },
}

In addition, when you use the theme starter to create a new Gatsby blog, it will automatically scaffold example blog posts for use with your site, such as the one located at /content/posts/hello-world/index.mdx:

---
title: Hello, world!
path: /hello-world
---
I'm an example post!

Now, when you run gatsby develop, you’ll see a fully fledged blog in your local development server, which will update normally as you modify files in the codebase and save them. And whenever you need to update the theme to a new version due to a new release, you can do so by updating gatsby-theme-blog’s entry to that version in package.json (for example, changing ^3.0.0 to ^4.0.0 when that version is released):

{
  "name": "gatsby-starter-blog-theme",
  "private": true,
  "version": "0.0.1",
  "scripts": {
    "develop": "gatsby develop",
    "start": "gatsby develop",
    "build": "gatsby build",
    "clean": "gatsby clean"
  },
  "license": "0BSD",
  "dependencies": {
    "gatsby": "^3.8.1",
    "gatsby-theme-blog": "^3.0.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "theme-ui": "0.7.3"
  }
}

and running the following to fetch the desired version:

$ npm install gatsby-theme-blog
Note

Though this section covered only gatsby-theme-blog and gatsby-starter-theme-blog, all Gatsby theme starters function this same way.

Using a Theme in an Existing Site

As you saw in the previous section, while it’s perfectly feasible to spin up a new Gatsby site based on a theme thanks to theme starters, it’s often the case that you already have a Gatsby implementation in progress that can’t benefit from a theme starter. In this scenario, you’ll need to install a theme into a preexisting Gatsby site.

Fortunately, since Gatsby themes, like plugins, are simply Node.js packages at their core, you can use the normal approach to install dependencies to add the theme by executing the following in the root of your Gatsby site:

$ npm install gatsby-theme-blog

Just like plugins, Gatsby themes can also accept theme options for configuration. When you add gatsby-theme-blog to your gatsby-config.js file, for instance, you can configure under which base directory the blog posts are represented by passing in a basePath option, as seen in this example:

module.exports = {
  plugins: [
    {
      resolve: `gatsby-theme-blog`,
      options: {
        // basePath defaults to `/`.
        basePath: `/blog`,
      },
    },
  ],
}
Note

Though gatsby-theme-blog is an example of a public theme, which is available on registries like NPM, it’s perfectly appropriate to create private themes that remain proprietary and closed-source. NPM and the GitHub Package Registry allow hosting of private themes.

Using Multiple Gatsby Themes

One of the characteristics of Gatsby themes that makes them unique, as we’ll see later in this chapter, is their composability. Though theme composition is a topic deserving of its own examination, for now it’s enough to know that the fact that Gatsby themes are composable means that you can install multiple themes in a single Gatsby site in much the same way you can install an arbitrary number of plugins.

One example of this in the Gatsby ecosystem is the gatsby-starter-theme project, which consists of two Gatsby themes composed together: gatsby-theme-blog, which we’ve already seen in this chapter, and gatsby-theme-notes, which is for bite-sized pieces of content that aren’t blog posts in their own right. Creating a new site based on gatsby-starter-theme, which you can do with the following command, outputs a gatsby-config.js file with two prepopulated themes:

$ gatsby new gtdg-ch10-using-multiple-themes gatsbyjs/gatsby-starter-theme

In the following Gatsby configuration file, for example, gatsby-theme-blog is configured with its default settings, while gatsby-theme-notes accepts two theme options:

module.exports = {
  siteMetadata: {
    title: `Shadowed Site Title`,
  },
  plugins: [
    {
      resolve: `gatsby-theme-notes`,
      options: {
        mdx: true,
        basePath: `/notes`,
      },
    },
    // with gatsby-plugin-theme-ui, the last theme in the config
    // will override the theme-ui context from other themes
    { resolve: `gatsby-theme-blog` },
  ],
}

According to the theme options configured here, a blog will be available at the root URL (/), and notes will be available from /notes.

Note

For a comprehensive end-to-end tutorial that demonstrates how to use multiple themes together in a working Gatsby site, consult the Gatsby documentation.

Creating Gatsby Themes

It’s often the case that no existing theme in the Gatsby ecosystem quite satisfies your requirements. If you’re implementing multiple Gatsby sites that share a certain modicum of functionality based on a theme, it may be a good idea to write your own theme. In this section, we’ll examine how to create new themes using Gatsby’s theme workspace starter. We’ll also look at common conventions for Gatsby themes, and how to convert starters into themes.

Creating New Themes

Because Gatsby has a vested interest in the community contributing themes back into the ecosystem, it provides an official starter for creating themes from scratch known as gatsby-starter-theme-workspace. This starter can be used to scaffold a new theme alongside a site that you can use to try out the theme:

$ gatsby new gtdg-ch10-creating-themes gatsbyjs/gatsby-starter-theme-workspace

When you change directories into the codebase, you’ll find a directory structure that looks like this, with an example site living alongside the theme directory:

.
├── example
│   ├── src
│   │   └── pages
│   │       └── index.js
│   ├── gatsby-config.js
│   ├── package.json
│   └── README.md
├── gatsby-theme-minimal
│   ├── gatsby-config.js
│   ├── index.js
│   ├── package.json
│   └── README.md
├── .gitignore
├── package.json
└── README.md

The Gatsby theme workspace starter produces two Yarn workspaces in order to ease the process of theme development. Leveraging Yarn workspaces allows Gatsby to manage two or more packages within a single directory (in this case our root folder). This means that we can maintain multiple themes and example sites within a single unified project, all while still enabling dependency updates to new versions, as Yarn continues to track the dependencies.

Note

You can also develop your theme as a local theme, using the same mechanism we used earlier to leverage local plugins. This would allow you to use techniques like yarn link and npm link, though Gatsby recommends using Yarn workspaces instead. For more information about the motivations behind using Yarn workspaces, consult the article “Setting Up Yarn Workspaces for Theme Development” on the Gatsby blog.

To familiarize ourselves with this unusually structured codebase, let’s inspect a few key elements: namely the package.json file, the /gatsby-theme-minimal directory, and the /example directory.

First let’s take a look at package.json, in the root of the new project:

{
  "name": "gatsby-starter-theme-workspace",
  "private": true,
  "version": "0.0.1",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "yarn workspace example build"
  },
  "workspaces": ["gatsby-theme-minimal", "example"]
}

There are two things to notice here:

  1. The build script leverages Yarn workspaces to build only the example site.

  2. We have a new workspaces array that accounts for the example site and the in-progress scaffolded theme.

The /gatsby-theme-minimal directory is where we’ll be doing the bulk of our theme development, and inside we find three files besides README.md:

  • gatsby-config.js is another Gatsby configuration file that represents the configuration for our theme, not for the project in general.

  • index.js is the entry point for our theme; since all themes are technically plugins, Gatsby requires this entry point, even if it is empty, so it can treat the theme like a plugin.

  • package.json lists the dependencies for our theme when installed by theme users and should include Gatsby as a peer dependency.

Finally, the /example directory contains a fully fledged Gatsby site that both installs and leverages the theme from its local directory, thus helping us avoid the back-and-forth friction of dependency management while we’re developing a theme. Inside, we find two salient items besides README.md and package.json:

  • gatsby-config.js is yet another Gatsby configuration file that represents the configuration for our example site (such as plugins extraneous to our theme), not for the theme or project in general.

  • The /src directory contains the source code for any Gatsby pages (and added components), just like in a normally functioning independent Gatsby site.

Note

For a comprehensive end-to-end tutorial on creating themes, consult the Egghead course on authoring Gatsby themes and its accompanying walkthrough in the Gatsby documentation.

Gatsby Theme Conventions

Because Gatsby themes are relative newcomers to the Gatsby ecosystem, having only emerged in the last several years as of this writing, theme conventions are still solidifying. Nonetheless, there are certain best practices that all Gatsby themes should follow in order to ensure their proper usage. In this section, we’ll cover naming conventions, separation of concerns, theme customization, and semantic versioning for Gatsby themes.

Nomenclature and required directories

Without exception, the name of every Gatsby theme must begin with the prefix gatsby-theme-. This is to ensure that Gatsby can effectively identify theme packages when it performs build-time compilation.

As we saw from the example of gatsby-theme-blog, some themes are responsible for scaffolding directories to give guidance to theme users and to house certain important information. It’s often the case that we need to prepare some required directories that will be leveraged by other plugins or that scaffold a required folder, such as the src/pages directory. We can use the onPreBootstrap lifecycle hook to initialize required directories so builds don’t fail.

In this example, we create a gatsby-node.js file to indicate to Gatsby that in order for the theme to function properly, we need two directories (Gatsby’s usual src/pages directory as well as a posts directory):

exports.onPreBootstrap = ({ store, reporter }) => {
  const { program } = store.getState()
  const dirs = [
    path.join(program.directory, "posts"),
    path.join(program.directory, "src/pages"),
  ]
  dirs.forEach(dir => {
    if (!fs.existsSync(dir)) {
      reporter.log(`Creating the ${dir} directory`)
      mkdirp.sync(dir)
    }
  })
}

Using Gatsby lifecycle hooks within gatsby-node.js can help you avoid some of the common pitfalls that occur when others try to use your Gatsby theme, especially missing directories that are required for use by the theme. Naming your theme according to Gatsby conventions is also essential for the proper functioning of your theme in arbitrary Gatsby sites.

Separating data queries from rendering components

Up until now, most of the example Gatsby pages that we’ve seen have co-located data queries and data rendering in the same file to make it easier to connect the dots between a data query and the renderer that handles the response data. Many developers prefer to formalize a separation of concerns that distinguishes between components of code that are responsible for querying data and others that perform rendering.

In Gatsby themes, separating data queries into independent components distinct from those handling rendering is often preferable to the co-location common in Gatsby pages. This is due to the fact that Gatsby themes often provide code components that are intended to be shadowed (i.e., overridden).

Without a clean separation between data query code and rendering code, a theme user would need to understand both how to issue page queries and how to perform JSX rendering. When those components are separated into distinct files, it becomes much easier for theme consumers to override individual JSX components (e.g., BlogPostList or UserCard) without having to rewrite the page query or StaticQuery providing the data.

If you need to separate out page queries so they can be overridden, you can use a typical Gatsby template file to perform the necessary data collection and then provide that data to a separate component—here, a JSX component known as <BlogPostList /> (src/components/blog-post-list.js):

import React from "react"
import { graphql } from "gatsby"
import PostList from "../components/blog-post-list"

export default function MyPostsList(props) {
  return <BlogPostList posts={props.allMdx.edges} />
}

export const query = graphql`
  query {
    allMdx(
      sort: { 
        order: DESC,
        fields: [frontmatter___date]
      }
      filter: {
        frontmatter: {
          draft: {
            ne: true
          }
        }
      }
    ) {
      edges {
        node {
          id
          frontmatter {
            title
            path
            date(formatString: "MMMM DD, YYYY")
          }
        }
      }
    }
  }
`

If you need to separate out static queries from a non-page component so they can be overridden, you can use the layout component to pass the response data from the data query to smaller rendering components as React props. In the following example, our layout component issues a static query to fetch site metadata, which is then used to populate smaller components within the layout:

import React from "react"
import { useStaticQuery, graphql } from "gatsby"

import Header from "../header.js"

const Layout = ({ children }) => {
  const {
    site: { siteMetadata },
  } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
          }
        }
      }
    `
  )

  const { title } = siteMetadata

  return (
    <>
      <Header title={title} />
      <main>{children}</main>
    </>
  )
}

export default Layout

Whether you perform data querying from the standpoint of a page component (with typical page queries) or a non-page component (with static queries), it’s important to allow theme users who wish to override functionality to shadow either the data querying or the rendering mechanism rather than struggling with a component that does both. This is one way that themes differ from normal page queries: they are intended to be readable by other developers, not just ourselves or our immediate developer teams.

Providing data customization in themes

Data queries and response data are typical places where theme users may wish to override the default data in place. For instance, consider one of the most common queries issued by Gatsby: a query to fetch site metadata such as the site title and description. A theme can provide a default query that pulls from metadata the user set in the gatsby-config.js file of the site that is using the theme.

Gatsby themes allow the use of hooks to issue arbitrary static queries at any point in other code throughout the Gatsby site. For instance, here’s an example of a hook, useSiteMetadata, that issues a static query (which doesn’t need a particular component type as it can be executed anywhere) to fetch the required information from the surrounding site’s Gatsby configuration file:

import { graphql, useStaticQuery } from "gatsby"

export default function useSiteMetadata() {
  const data = useStaticQuery(graphql`
    {
      site {
        siteMetadata {
          title
          social {
            twitter
            github
            instagram
          }
        }
      }
    }
  `)

  return data.site.siteMetadata
}

This hook can then be used in the context of other components within the theme, such as a shared header component that can also be accessed within the Gatsby site. Because we’ve used a static query, this hook could feasibly be used anywhere in our theme and on any site that has the theme installed:

import React from "react"
import { Link } from "gatsby"

import useSiteMetadata from "../hooks/use-site-metadata"

export default function Header() {
  const { title, social } = useSiteMetadata()
  return (
    <header>
      <Link to="/">{title}</Link>
      <nav>
        <a href={`https://twitter.com/${social.twitter}`}>Twitter</a>
        <a href={`https://github.com/${social.github}`}>GitHub</a>
        <a href={`https://instagram.com/${social.instagram}`}>Instagram</a>
      </nav>
    </header>
  )
}

When we run gatsby develop in the example folder, we can see the installed theme functioning correctly. These sorts of hooks can be used to override and provide defaults for a variety of data querying needs, allowing you to separate concerns between the data queries that drive your site as a whole and the unique rendering mechanisms you provide for the data you need.

Releasing and versioning Gatsby themes

Releasing themes works precisely the same way as releasing a plugin (see Chapter 9), so I won’t repeat the instructions to publish to the Gatsby Plugin Library and the NPM package registry in this section. Because themes, like plugins, are typically intended for use by other Gatsby developers rather than strictly for private use, the same best practices in terms of semantic versioning also apply.

Gatsby outlines several best practices for semantic versioning in themes that account for various changes you may need to make as you evolve your theme, such as removing queries that certain sites may depend on. As we saw in the previous chapter, in semantic versioning there are three types of releases. These are summarized here with information relevant for themes:

  • Patch (0.0.x) releases are bug fixes that are backward-compatible, so no breaking changes are made. Public APIs should remain unaffected. For themes, a patch release might entail fixing a bug in a component, adding error handling or default values, or upgrading upstream dependencies to their latest minor and patch versions.

  • Minor (0.x.0) releases introduce new features that remain backward-compatible, so no changes break existing theme users’ sites. In minor versions, existing public APIs should remain unaffected. For themes, a minor release might entail adding new pages or queries, adding new theme configuration options, rendering additional data, adding new props to a component, or adding a new MDX shortcode.

  • Major (x.0.0) releases include any new bug fixes or features that are not backward-compatible, meaning these changes will break Gatsby sites using the theme. In major version releases, a migration path or guide should be provided in order to upgrade the theme to the latest version. For themes, a major version release might include changing a filename (which requires updates to any files that shadow that file), removing or modifying props accepted by a component, modifying queries, removing theme configuration options or modifying how they work, removing attributes in schema definitions, removing default data, or changing plugins or plugin configuration.

Note

For a more detailed overview of semantic versioning in Gatsby themes, consult the semantic versioning section of the guide to Gatsby theme conventions and Semantic Versioning 2.0.0.

Converting Starters into Themes

Earlier, we explored how to build themes from scratch using the Gatsby theme workspace starter. But this isn’t the only way to create a new theme; Gatsby developers can also convert starters into working Gatsby themes. In fact, the Gatsby documentation pinpoints this conversion path from starters to themes as one of the major motivations for how themes were designed in the first place.

Recall that a starter is a sample Gatsby boilerplate that, once modified, loses all tether to its original source. Meanwhile, a theme is a type of plugin that can be updated and modified according to version updates and modifications made by theme users. In many cases, you may wish to convert a starter into a theme in order to allow the theme user to receive updates to code that cannot be handled by starters as a rule.

In other words, modifying a starter immediately forces the developer to track all of their changes, whereas shadowing a theme means overriding defaults that are updated as new theme versions are released.

The first step to convert a starter is to modify your package.json file so it represents the typical characteristics expected of a theme. To ensure Gatsby itself and other developers who choose to use your theme are able to discover it, follow these steps:

  1. Modify the name to match Gatsby naming conventions. Recall that whereas Gatsby starter names are prefixed with gatsby-starter-, Gatsby theme names begin with gatsby-theme-. For instance, if your starter is named gatsby-starter-hello-world, you can change it in package.json to gatsby-theme-hello-world. If you plan to release your theme publicly as a registered package, ensure the namespace you plan to occupy is unused.

  2. Include required development dependencies. Because themes are packages in their own right but cannot be used in isolation from a Gatsby site, it’s important to clarify to users of your package that there are certain required dependencies. Every theme should include gatsby, react, and react-dom as peerDependencies, as in the following example:

    {
      "name": "gatsby-theme-blog",
      // ...
      "peerDependencies": {
        "gatsby": "^3.0.0",
        "react": "^17.0.0",
        "react-dom": "^17.0.0"
      },
    }
  3. Create an index.js file at the project root. Every JavaScript package must include an index.js file, even if it’s empty, in order to be executable as JavaScript. This is a limitation of Node.js, as Node.js seeks an index.js file first and foremost when Gatsby resolves the theme dependency. You could also provide an alternative file for the main key, as seen here:

    {
      "name": "gatsby-theme-blog",
      "version": "3.0.0",
      "description": "A Gatsby theme for miscellaneous blogging with a >>>
    dark/light mode",
      "main": "other-alternative-file.js",
    }

The second step is handling path resolution. Starters and themes differ in one fundamental way: whereas starters are composed of executable Gatsby code, themes converted from starters are no longer automatically executed whenever a Gatsby CLI command is run, as all of the logic is now housed in a dependency. Because the Gatsby CLI looks for pages to build within the site and not in dependencies, you may encounter errors after following the preceding steps that have to do with unresolved paths.

To ensure that the paths represented in your starter are created whenever Gatsby builds a site based on your new theme, you can enforce the creation of those required paths using the createPage function in gatsby-node.js, as follows:

const createArticles = (createPage, createRedirect, edges) => {
  edges.forEach(({ node }, i) => {
    // Additional logic goes here.

    createPage({
      path: pagePath,
      component: require.resolve(`./src/templates/article.js`),
      context: {
        id: node.id,
        prev,
        next,
      },
    })
  })
}

If you had this code in your starter’s gatsby-node.js file, it would simply look for the template found in the site’s src/templates/article.js rather than the location you intend: node_modules/gatsby-theme-minimal/src/templates/post.js. To avoid this issue, you use require.resolve to point to a local template rather than path.resolve, which would have Gatsby seek the template in the surrounding site instead.

Pay close attention to where these paths need to switch their resolution, now that you have two places where paths need to be represented properly: your surrounding Gatsby site and your theme dependency. Another common place where path.resolve invocations may need to be substituted with require.resolve is in your theme’s gatsby-config.js file.

Note

Most starters use Gatsby’s default behavior to account for pages from the path src/pages, but some starters choose to supply pages from other directories too. In order to enable this same behavior in your theme, you’ll need to use the gatsby-plugin-page-creator plugin, which allows you to source pages from custom directories. After installing the plugin, you can configure the custom directory in your gatsby-config.js file:

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-page-creator`,
      options: {
        path:  path.join(__dirname, `src`, `{custom-directory}`),
      },
    },
  ],
}

Theme Shadowing

As I’ve alluded to a few times in this chapter, Gatsby themes are particularly compelling not just as extensions of existing Gatsby functionality, but also for their flexibility. As we’ve seen, theme composition permits multiple Gatsby themes to be arbitrarily installed into a single Gatsby site. Theme shadowing, meanwhile, allows end users of Gatsby themes to replace any file in the src directory with their own overriding implementation instead.

In this section, we’ll cover how theme shadowing works and its various forms. In the process, we’ll look at several real-world examples to understand how theme shadowing adds considerable value to the relatively new concept of Gatsby themes.

Gatsby themes allow you to shadow any file that is handled by Webpack for bundling. This means theme users can override not only individual JavaScript or TypeScript files but also JSON and YAML files as well as CSS stylesheets that are imported into the site. For instance, consider a scenario where we have a theme that provides an article component for content articles. Theme shadowing allows us to override that article component with our own desired logic.

To demonstrate this, let’s walk though one of Gatsby’s official themes, gatsby-theme-blog, which is also a wonderful reference for theme developers.

Note

Because Gatsby themes are relatively new entrants to the Gatsby developer experience, Gatsby recommends working with more “monolithic” themes before decomposing them into composable themes. For more information about theme composition and shared global layouts, consult the theme composition page in the Gatsby documentation.

Theme Shadowing in gatsby-theme-blog

To begin, let’s create a new site based on the Gatsby blog theme starter, which will automatically set up what we need:

$ gatsby new gtdg-ch10-theme-shadowing gatsbyjs/gatsby-starter-blog-theme

Once you’ve installed the starter, change directories into the scaffolded site. You’ll encounter an internal directory structure that looks like the following:

gtdg-ch10-theme-shadowing
├── content
│   ├── assets
│   │   └── avatar.png
│   └── posts
│       ├── hello-world
│       │   └── index.js
│       └── my-second-post.mdx
├── src
│   └── gatsby-theme-blog
│       ├── components
│       │   └── bio-content.js
│       └── gatsby-plugin-theme-ui
│           └── index.js
├── .gitignore
├── .prettierrc
├── gatsby-config.js
├── LICENSE
├── package-lock.json
├── package.json
└── README.md

As you can see, in our src directory, we have a gatsby-theme-blog directory containing just a few files, namely bio-content.js and colors.js. These are files that shadow, or override, the identically named files found in the theme plugin itself. If you were to open the node_modules directory containing the installed theme, you would see the unshadowed versions of those files as well as other shadowable files from the theme.

Open src/gatsby-theme-blog/components/bio-content.js, where you’ll find the following code:

import React from "react"
import { Themed } from "theme-ui"

/**
 * Change the content to add your own bio
 */

export default function Bio() {
  return (
    <>
      This is where <Themed.a href="http://example.com/">your name</Themed.a>
      {` `}
      goes.
      <br />
      Or whatever, you make the rules.
    </>
  )
}

With this bio-content.js component shadowing the bio-content.js component in our installed plugin, we can now rewrite the file to our heart’s content to provide the specific bio we want to provide instead. This will override the logic in the upstream theme’s file and apply the custom logic we’ve written in the shadowed component. As you can see, any JavaScript component within a theme can be shadowed. But what about other files, like files provided by a theme’s dependency rather than the theme itself?

Shadowing Other Files

The gatsby-theme-blog theme incidentally installs the gatsby-plugin-theme-ui plugin, which provides a variety of CSS styles to the theme. This plugin includes a preset, gatsby-theme-ui-preset, which is used by the gatsby-theme-blog theme. In order to shadow a file from a plugin that a theme depends on, we can use much the same approach as before.

Within the src directory, the directory gatsby-plugin-theme-ui contains shadowed files that will override the installed gatsby-plugin-theme-ui dependency’s files. For instance, to override the index.js file in the installed plugin within our site, we can create a shadow file, index.js, and pass in overriding information. Here’s an example:

const darkBlue = `#007acc`
// const lightBlue = `#66E0FF`
const blueGray = `#282c35`

const theme = {
  colors: {
    text: blueGray,
    primary: darkBlue,
    heading: blueGray,
  },
}

export default theme

Gatsby will combine these values with those already found in gatsby-theme-ui-preset in order to produce a synthesized version of the index.js file and style values. Whenever these values conflict with those found in the installed plugin, your shadowed files will take precedence.

Remember that any source file handled by Webpack, regardless of extension, can be shadowed to override the upstream theme. Consider, for example, a situation in which you have a theme (let’s call it gatsby-theme-layout) that provides a CSS stylesheet such as components/layout.css. Gatsby developers can create a new folder within their site (src/gatsby-theme-layout) to provide files that shadow the theme’s CSS:

/* src/gatsby-theme-layout/components/layout.css */
body {
  background-color: rebeccapurple;
}

This would then override the entirety of the CSS stylesheet in the upstream theme with the new shadow CSS stylesheet.

Note

Gatsby can intelligently guess what extension the component or file that needs to be imported carries when it comes to JavaScript and TypeScript. For instance, if you import a TypeScript file such as src/components/article.tsx as shown here, Gatsby can infer that article.tsx is the correct component:

import Article from "./article"

This is particularly useful in theme shadowing, because you can write an article.js component that shadows an article.tsx TypeScript component without running into transpilation issues.

Extending Shadowed Files

Theme shadowing doesn’t just allow you to override entire files, like we’ve seen up to this point. After all, one of the issues with the CSS override example from the previous section is that you may not want such a small stylesheet to override every single line of CSS in the upstream file. Instead of overriding theme files, sometimes we need to extend those theme files through theme shadowing.

The primary method of extending shadowed files is to import the component you intend to shadow and then render it, in the process providing any additional information you want to include. Consider the following example adapted from the Gatsby documentation, which depicts a bio component that needs to pull from a theme’s components and extend them into a shadow component with extended functionality:

import React from "react"
import { Avatar, MediaObject, Icon } from "gatsby-theme-blog"

export default function Bio({ name, bio, avatar, twitterUrl, githubUrl }) {
  return (
    <MediaObject>
      <Avatar {...avatar} />
      <div>
        <h3>{name}</h3>
        <p>{bio}</p>
        <a href={twitterUrl}>
          <Icon name="twitter" />
        </a>
        <a href={githubUrl}>
          <Icon name="github" />
        </a>
      </div>
    </MediaObject>
  )
}

In this example, all of the code within the <MediaObject /> component allows us to add to the theme’s default rendering of that component. If we didn’t extend the component in this way, we would instead need to copy the entire component from the upstream theme as a separate component in our site.

Tip

The previous example imports the components from the theme dependency, but you can also write more efficient code by importing the shadowed component into another component by referring to it directly, like this:

import { Author } from "gatsby-theme-blog/src/components/bio"

Though theme shadowing is an effective way to use themes for more robust architectures in Gatsby, there’s always a risk of shadowing too much. As a best practice, if you’re finding that you’re overriding or extending a large number of files in the upstream theme, it might be a good idea to consider a fork of the theme instead and create a distinct theme. Gatsby officially supports this model by providing “core” themes, like gatsby-theme-blog-core, a dependency for gatsby-theme-blog, that can serve as foundations for forks of gatsby-theme-blog or new themes.

Note

For more information about leveraging shadowed components with distinct prop APIs in React, consult the Gatsby documentation. The documentation also contains a guide on how theme shadowing and theme composition operate under the hood.

Conclusion

Though themes differ from plugins and starters in many significant ways, they are useful for a variety of use cases, particularly to allow for packaged portions of functionality repeated across multiple sites to be managed gracefully as a single dependency. Though many Gatsby users will use Gatsby themes to override aesthetic elements to their heart’s content, others will override data queries or other functionality to more richly customize their sites with prefabricated code.

Gatsby themes also introduce two new concepts to our vocabulary: composition and shadowing. Theme composition is compelling because it allows us to combine arbitrary themes in a single Gatsby site. Meanwhile, theme shadowing is what allows us to override the files in individual themes to extend our sites with custom code.

Though themes are relatively new to the Gatsby ecosystem, they portend a new era of Gatsby development. And while this chapter concludes our examination of extending Gatsby, we will next turn to how to ensure Gatsby is ready for the real world through debugging and testing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.237.0.123