provide a powerful way to share an opinionated set of configurations across multiple Gatsby sites. The features built into your Gatsby Theme will be abstracted out of your site and packaged as a dependency, providing an efficient way to develop similar Gatsby sites. Gatsby Themes In this tutorial, we’ll set up a development workspace to build and demo a simple theme. Our workspace will be equipped with Typescript, ESLint, and Husky, so we can quickly start developing with a team. Our theme will support MDX pages, an MDX blog, frontmatter, syntax highlighting in code blocks, responsive images, and a custom theme built with the spec. Theme-UI Skip to the end View Demo Site → Build a Gatsby site using this theme → Build a Gatsby theme using this workspace → View source code → As you follow this guide, I encourage you to periodically reference my source code and commit history. Prerequisites Although this guide is fairly thorough, it will help if you have some basic experience with Yarn workspaces, Gatsby, and Gatsby themes. If you don’t, I recommend skimming through these resources to get up to speed. What are Gatsby themes? Building themes Gatsby theme authoring (video course) Most Gatsby themes leverage the concept of and to make theme development more efficient. It may be worth reading up on these topics if you aren’t familiar. Shadowing in Gatsby Theme-UI 1. Set up workspace 1.1 Clone a starter workspace repo is a very simple and minimal starter gatsby-starter-theme-workspace is a good starter for MDX blog support (I used this one) gatsby-theme-mdx 1.2 Make the repo your own The root of your repo Should have the following files: , , README package.json .gitiginore Should have the following folders: and . Feel free to rename these folders. theme demo The root should have workspaces for and folders. package.json theme demo The theme that we will be creating 1. Should have its own , , , and gatbsy files README package.json .gitignore 2. The main file can be left empty index.js 3. Rename the contents of the to be your own theme package.json You can name your theme whatever you want, but you should make sure it is scoped using your if you plan on publishing it. npm username (You should create an account now if you don’t have one). I’ll be using as the name of my theme. @shetharp/gatsby-theme-candor For the rest of the tutorial, you should replace @shetharp with your npm username and gatsby-theme-candor with your theme name. The demo site that will use your theme 1. Should have its own , , , and gatsby files README package.json .gitignore 2. Rename the contents of the to be your own demo package.json Make sure under the dependencies section, you have a dependency for your theme. The name of this dependency should match the name you set in the . theme package.json Because we have not published the theme yet as an npm package and will be developing it locally, set the version to so that yarn knows to look for the package locally. * The dependencies section should look something like this: : { : , : , : , : }, "dependencies" "gatsby" "^2.13.1" "@shetharp/gatsby-theme-candor" "*" "react" "^16.8.6" "react-dom" "^16.8.6" 3. Update the file. Make sure under plugins, you include the name of your theme. It should match the name you set in the . For example: demo gatsby-config.js theme package.json plugins: [{ : , : {} }] resolve '@shetharp/gatsby-theme-candor' options 1.3 Make sure your setup is working 1. Install dependencies in your workspace yarn 2. Run your site demo yarn workspace demo develop This command tells yarn to run the develop script from the package.json in your demo workspace folder. 2. Set up Typescript, ESLint, and Husky We’ll be adding a few dev dependencies and configurations to help enforce good code quality and catch errors, especially when working with a team. You can skip this section if the juice isn’t worth the squeeze. 2.1 Set up Typescript Gatsby comes with , but we need to add some configurations since we are working with Yarn workspaces. native support for Typescript 1. Add Typescript as a dev dependency to your workspace yarn add -W -D typescript The flag tells yarn to add the dependency to your workspace's -W root package.json 2. Create a file in the of your repo tsconfig.json root { : { : , : , : , : [ , ], : , : , : , : , : , : }, : [ , ], : [ , , ] } "compilerOptions" "target" "esnext" "module" "commonjs" "jsx" "react" "lib" "dom" "es2017" "moduleResolution" "node" "allowSyntheticDefaultImports" true "strict" true "noEmit" true "skipLibCheck" true "esModuleInterop" true "include" "./demo/src/" "./theme/src/" "exclude" "node_modules" "demo/node_modules" "theme/node_modules" 3. Add a script in your type-check root package.json : { : } "scripts" "type-check" "tsc --noEmit" If you run yarn it should run the typescript compiler. type-check 4. Add an example Typescript page in demo/src/pages/example.tsx React ; { PageProps } ; { ( <h1>Path:</h1> <pre>{props.path}</pre> import from "react" import from "gatsby" export default ( ) function TypescriptExample props: PageProps return <> Example page using typescript. ); } </> 5. Test that it is working. Run to make sure Gatsby is running, then navigate to to see the page. yarn workspace demo develop http://localhost:8000/example If you modify the file to cause a typescript error (e.g. ), you may notice that it updates your website, but Gatsby doesn't necessarily catch the error and crash. But if you run , it should catch that error. In the rest of the set up, we'll automate type-checking into our workflow. <pre>{props.asdf}</pre> yarn type-check 2.2 Set up ESLint We'll use ESLint to enforce consistent code syntax and formatting. (Learn more: Using ESLint and Prettier in a TypeScript Project ). 1. Add ESLint along with its plugins for Typescript, React, and Prettier as dev dependencies to your workspace root yarn add -W -D eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react @typescript-eslint/eslint-plugin @typescript-eslint/parser 2. Add an file in your workspace .eslintignore root node_modules **/node_modules/** **/.cache/** **/build/** **/public/** 3. Add an file in your workspace .eslintrc.js root .exports = { : { : , : , }, : , parserOptions: { : , sourceType: , ecmaFeatures: { : , }, }, : { : { : , }, }, : [ , , , , ], : { : [ , { : [ , ] }], : , : , : , : [ , { : }], }, }; module env browser true node true parser "@typescript-eslint/parser" // Specifies the ESLint parser ecmaVersion 2020 // Allows for the parsing of modern ECMAScript features "module" // Allows for the use of imports jsx true // Allows for the parsing of JSX settings react version "detect" // Tells eslint-plugin-react to automatically detect the version of React to use extends "plugin:react/recommended" // Uses the recommended rules from @eslint-plugin-react "plugin:@typescript-eslint/recommended" // Uses the recommended rules from the @typescript-eslint/eslint-plugin "prettier/@typescript-eslint" // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. rules // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs "no-console" "error" allow "warn" "error" "@typescript-eslint/explicit-function-return-type" "off" "@typescript-eslint/no-explicit-any" "warn" "@typescript-eslint/no-var-requires" "off" "react/prop-types" "warn" skipUndeclared true 4. Add a file in your workspace .prettierrc.js root .exports = { : , : , }; module printWidth 120 proseWrap "preserve" 5. Add and scripts in your lint lint:fix root package.json : { : , : , : }, "scripts" "lint" "eslint . --ext .ts,.tsx,.js,.jsx" "lint:fix" "yarn lint --fix" "type-check" "tsc --noEmit" 6. Run to view any lint errors. If you need to, restart your IDE to see lint errors in your code. Run to auto fix most lint errors. You may need to fix remaining lint errors manually (or add comments if you're feeling lazy). yarn lint yarn lint:fix eslint-disable 2.3 Set up Husky We'll use Husky to run the linter and type-checker before we commit and push code to git. These pre-commit and pre-push hooks will allow us to frequently catch syntax inconsistencies and type errors. 1. Add husky and lint-staged as dev dependencies yarn add -W -D husky lint-staged 2. Add a file in your workspace .huskyrc.js root .exports = { : { : [ ], : [ ] } } module "hooks" "pre-commit" "lint-staged" "pre-push" "yarn type-check" 3. Add a file in your workspace .lintstagedrc root { : [ ], : [ ] } "*.{js,jsx,ts,tsx}" "yarn lint:fix" "{*.{json,md,mdx,yml,yaml}}" "prettier --write" Commit and push these changes in your repo to git. You should see Husky in action! 3. Style your theme Most Gatsby themes use for developing themeable . Theme UI websites 1. Add it as a dependency to your workspace, if it doesn't already include it. theme yarn workspace @shetharp/gatsby-theme-candor add gatsby-plugin-theme-ui 2. Add it as a plugin to your file theme's gatsby-config.js .exports = { : [ , ] } module plugins "gatsby-plugin-theme-ui" 3. To create your own theme, you'll need to leverage Shadowing in Gatsby to shadow the theme file from . Create a file in . Create a theme object by following the and make it the default export of the file. gatsby-plugin-theme-ui theme/src/gatsby-plugin-theme-ui/index.ts Theme UI Spec 4. You can build your theme from scratch if you're comfortable with Theme-UI, or you can base it off of a preset/existing theme. You can reference the on GitHub. gatsby-theme-candor theme file source code 5. If you're making lots of changes to your theme, you might want to consider creating a theme preview page to see all of your theme's styles and components in one place. You can reference the or its . Tip: gatsby-theme-candor Theme Preview page source code 4. Set up MDX for pages and posts To support in your theme, you will need to add the plugin if your theme doesn't have it yet. MDX gatsby-plugin-mdx yarn workspace @shetharp/gatsby-theme-candor add gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react Make sure to add it to your theme's gatsby-config.js file. You can learn more about the plugin options in the plugin's . official documentation .exports = { : [ { : , : { : [ , ], : { : .resolve( ), }, }, }, ] } module plugins resolve "gatsby-plugin-mdx" options extensions ".mdx" ".md" defaultLayouts default require "./src/templates/Page.tsx" 4.1 Set up Gatsby Source Filesystem 1. In order for us to support MDX queries, responsive images, and a content directory for posts, we'll need to add the plugin to our theme. frontmatter gatsby-source-filesystem yarn workspace @shetharp/gatsby-theme-candor add gatsby-source-filesystem 2. Next, we will add the plugin to our file and configure it to source posts and images. theme's gatsby-config.js (Gatsby will automatically source from src/pages , so we don't need to include it). .exports = { : [ { : , : { : , : , }, }, { : , : { : , : , }, }, ] } module plugins resolve "gatsby-source-filesystem" options name "posts" path "src/posts" resolve "gatsby-source-filesystem" options name "images" path "src/images" 3. Gatsby will throw an error if these paths don't exist in your set up. To avoid this, create folders for each of these paths (you can place empty files as placeholders within them) in your theme's workspace. 4. We also want to make sure consumers of our theme don't experience a Gatsby build error because one of our required paths doesn't exist for their set up. We can use the lifecycle hook to initialize these required directories for our users before Gatsby builds their site. Add this code to your file. This is a common . onPreBootstrap theme's gatsby-node.js Gatsby Theme Convention path = ( ); fs = ( ); mkdirp = ( ); exports.onPreBootstrap = { { program } = store.getState() dirs = [ path.join(program.directory, ), path.join(program.directory, ), path.join(program.directory, ), ] dirs.forEach( { (!fs.existsSync(dir)) { reporter.log( ) mkdirp.sync(dir) } }) } const require "path" const require "fs" const require "mkdirp" ( ) => { store, reporter } const const "src/pages" "src/posts" "src/images" => dir if `creating the directory` ${dir} 4.2 Set up Gatsby Sharp for images We will use Gatsby Sharp to support responsive optimized images in our site. 1. Install the plugins in your workspace theme yarn workspace @shetharp/gatsby-theme-candor add gatsby-plugin-sharp gatsby-transformer-sharp 2. Make sure to add these plugins to your file theme's gatsby-config.js .exports = { : [ , , ] } module plugins "gatsby-plugin-sharp" "gatsby-transformer-sharp" 3. We also need to install the plugin to support responsive images in our MDX content. gatsby-remark-images yarn workspace @shetharp/gatsby-theme-candor add gatsby-remark-images 4. Make sure to add this to your file under the plugin options for theme's gatsby-config.js gatsby-plugin-mdx .exports = { : [ { : , : { : [ , ], : { : .resolve( ), }, : [ resolve: , : { : , } ], }, }, ] } module plugins resolve "gatsby-plugin-mdx" options extensions ".mdx" ".md" defaultLayouts default require "./src/templates/Page.tsx" gatsbyRemarkPlugins "gatsby-remark-images" options maxWidth 800 4.3 Set up MDX Frontmatter We want our theme to support queries, so first to your files. For example: frontmatter add some frontmatter .mdx --- title: Hello World! Catchy title from frontmatter! author: Fina Mitai date: 2020 -07 -30 featureImage: ./redwood.jpg --- This is the markdown content. Lorem ipsum dolor sit **amet**. We will reference the frontmatter with and query it with graphQL in the next steps. pageContext At this point, your website should be rendering MDX pages in your demo site's directory with your theme styles applied. src/pages 5. Programmatically Creating Pages Because we have blog posts (in the directory) being sourced outside of , we need to give each post a slug for Gatsby to render the url into a page. (Alternatively, you could define the slug in the frontmatter, but in this example, we want Gatsby to for us). Luckily, there's an official plugin we can use to avoid writing all this configuration manually. src/posts src/pages generate slugs 1. Install the plugin to your workspace gatsby-plugin-page-creator theme's yarn workspace @shetharp/gatsby-theme-candor add gatsby-plugin-page-creator 2. Add to your file. Give it the option to create pages from the directory. gatsby-plugin-page-creator theme's gatsby-config.js src/posts .exports = { : [ { : , : { : , }, }, ] } module plugins resolve "gatsby-plugin-page-creator" options path "src/posts" 3. Update the options in your file to create pages for posts using a Posts template. We'll create the Posts template in the next step. gatsby-plugin-mdx theme's gatsby-config.js .exports = { : [ { : , : { : [ , ], : { : .resolve( ), : .resolve( ), }, : [ resolve: , : { : , } ], }, }, ] } module plugins resolve "gatsby-plugin-mdx" options extensions ".mdx" ".md" defaultLayouts default require "./src/templates/Page.tsx" posts require "./src/templates/Post.tsx" gatsbyRemarkPlugins "gatsby-remark-images" options maxWidth 800 4. Create a template for Posts in your file theme's src/templates/Post.tsx React ; { graphql, useStaticQuery, PageProps } ; Layout ; { Badge, Text } ; type PostProps = PageProps & { : { : { [k: string]: string }; }; }; Post: React.FC<PostProps> = { { children } = props; data = useStaticQuery(graphql ); ( <Badge variant="accent"> <Text variant="mono">Post template</Text> </Badge> <Badge variant="highlight" marginLeft={1}> {data.site.siteMetadata.title} </Badge> <h1>{props.pageContext.frontmatter.title}</h1> <span>{props.pageContext.frontmatter.author}</span> ); }; Post; import from "react" import from "gatsby" import from "../components/Layout" import from "theme-ui" export pageContext frontmatter const ( ) => props const const ` query { site { siteMetadata { title } } } ` return < > Layout {children} </ > Layout export default 5. Run to see the files in your demo site's directory get turned into pages! yarn workspace demo develop .mdx src/posts 6. Set up a Blog Index Page To list out all the pages in your website, you'll want to create a blog index page. In this example, we'll name this page the page. However, you can name this whatever you want, such as , , etc. Blog SiteIndex SiteMap 1. Create a new file in your workspace src/pages/blog.tsx demo 2. Add the following to the file React from ; { PageProps, Link, graphql } from ; { Layout } from ; { Styled } from ; const BlogIndex: React.FC<BlogIndexProps> = (props) => { const { data } = props; const { nodes: pages } = data.allSitePage; return ( <Layout> <Styled.h1>Blog Index</Styled.h1> <Styled.ul> {pages.map(({ id, path, context: { frontmatter } }) => ( <Styled.li key={id}> <Link to={path}> <code>{path}</code> </Link> {frontmatter?.title && ` -- ${frontmatter.title}`} </Styled.li> ))} </Styled.ul> </Layout> ); }; export BlogIndex; export const pageQuery = graphql` query AllPagesQuery { allSitePage { nodes { id path context { frontmatter { author date excerpt featureImage title } } } } } `; import "react" import "gatsby" import "@shetharp/gatsby-theme-candor" import "theme-ui" default 3. You can verify the graphql query works by trying it out in GraphiQL. Essentially, this query is getting all the pages in our site and providing their id, path, and frontmatter. In our component, we use the frontmatter to render the title of the page and the path to link to the page. BlogIndex 7. Deploy your demo to GitHub Pages We are going to deploy our demo site using GitHub Pages to make it easy to view the demo site without having to pull down and build the repo. For this example, we will be deploying the demo site to , where will be your username and will be the name of your repo on GitHub. https://shetharp.github.io/gatsby-theme-candor/ shetharp gatsby-theme-candor If you've ever , these steps should look familiar. deployed a Gatsby site using GitHub Pages 1. Install as a dev dependency to your workspace gh-pages demo yarn workspace demo add -D gh-pages 2. Because the root url of our website will have its repo name in it, we need to define a prefix in the file demo gatsby-config.js .exports = { : , } module pathPrefix "/gatsby-theme-candor" 3. Next, we add a script to our to make it easy to deploy with one command demo package.json { : { : } } "scripts" "deploy" "gatsby build --prefix-paths && gh-pages -d public" 4. Make sure your latest changes are committed to git. Then, change directory into your demo workspace and deploy! cd demo yarn deploy 5. Make sure you've configured your GitHub repo to source from the branch for deployments. gh-pages (You can follow these instructions to configure that). 8. Publish your theme to npm We're getting ready to publish our theme! Before we proceed, consider cleaning up or updating your , , and files to be well documented for your needs. Also, if you don't have an npm username, you should now. README package.json gatsby-config create an npm account 1. At the beginning of this walkthrough, when setting up your yarn workspace, you should have given your workspace a name in its file. Verify that the name you provided is , typically using your npm username (e.g. ). theme package.json namespaced "name": "@shetharp/gatsby-theme-candor" This will help the Gatsby and npm community keep track of who published the theme and avoid name collisions. You will have to use your own namespace for this step. 2. Verify that you are logged into npm by running npm whoami If you're not logged in, run to enter your username, password, and email. npm login 3. Change directories into your theme workspace cd theme 4. Publish to npm! npm publish --access-public You should be able to see your newly published package in your npm profile. 5. If you decide to publish new updates to your theme later on, you will need to update the version number in your . Verify that the changes you've made to your theme are reflected in your updated version number. It is to use semantic versioning to indicate breaking changes or patches. theme package.json common practice Conclusion Congrats on setting up your Gatsby Theme! In this tutorial we made an effort to set up a robust repository for theme development with a team--so share it with others and start collaborating! As you continue configuring your theme to meet your needs, I encourage you to view the demo site, source code, and commit history. gatsby-theme-candor View Demo Site → Build a Gatsby site using this theme → Build a Gatsby theme using this workspace → View source code → If you found this tutorial to be a useful starting point, feel free to build your next gatsby site or theme on top of . Let me know what you build ! gatsby-theme-candor @shetharp