Eric
Howey.

There is a hidden page you can visit at /you-are-awesome. Didn't want you to miss out on the fun!
Content has not been updated for more than 2 years, proceed with caution.

How to setup a Gatsby themes monorepo

I manage a Gatsby themes monorepo that currently has 11 themes and 8 starters with over 1500 commits - lots of moving parts to keep organized. At the end of this tutorial you will have a Gatsby themes monorepo setup with automated testing, automated dependency management, automated publishing, continuous integration, and continuous deployment!

Here are the tools (all free) we will be reviewing in this post:

This post assumes you have some basic familiarity with Gatsby, Gatsby themes, NPM and GitHub.

This is a long one so get a fresh cup of coffee and let’s go!

Use a template

Gatsby Theme Empty is a GitHub template repository that is preconfigured with the tools mentioned above so you can quickly get started with your own project and avoid lots of copy/paste. I intentionally kept this repository as “empty” as possible, it is only a hello world, and instead focuses on the tooling for the monorepo itself.

  1. Click the Use This Template button on GitHub which will prompt you to clone the template into a new repo under your own username.

  2. Clone your new repo locally and open it in your code editor of choice.

Explore the files and folder structures. Themes and starters are located in their respective folder. There are also folders for Cypress.io and GitHub. You will notice some configuration files for the tools and that is about it. My hope with this template is that there is little, if anything, you need to delete before you can start working.

Find and replace magic

Update naming across your files and folders using find and replace.

  1. Do a find and replace for empty -> your-theme-name. Click replace all. So for example if your theme was going to be named gatsby-theme-kickoff you would find and replace empty -> kickoff.

  2. Update the folder names to properly reflect your new theme and starter. You should have folders that look like themes/gatsby-theme-your-theme-name and starters/gatsby-starter-your-theme-name.

Update package.json files

Browse to themes/gatsby-theme-monorepo-tutorial/package.json and starters/gatsby-starter-monorepo-tutorial/package.json and update any fields you need to like the author, description and repo links. The name field should already be correct if you followed the above steps correctly. Note that the starter’s package.json has a private: true field set on it so it is not published to NPM. Typically people only publish the theme to NPM.

Setup Yarn workspaces

Run yarn install in your terminal to start installing all of the packages and creating a lock file.

Yarn workspaces abstracts all of the complicated package management necessary for local theme development. In practical terms this means you can seamlessly work on both your theme and your site at the same time and only push changes to NPM when you are ready.

Yarn workspaces are configured through the root package.json file with a special workspaces field which tells Yarn where to look for additional dependencies and related packages.

Here is an example package.json, note the use of custom scripts to shorten the workspaces syntax and that private is set to true.

{
  "private": true,
  "workspaces": ["starters/*", "themes/*"],
  "name": "gatsby-theme-monorepo",
  "devDependencies": {
    "@testing-library/cypress": "^6.0.0",
    "cypress": "^5.0.0",
    "gatsby-cypress": "^0.4.10",
    "lerna": "^3.22.1",
    "start-server-and-test": "^1.11.3"
  },
  "scripts": {
    "develop:tutorial": "yarn workspace gatsby-starter-monorepo-tutorial develop",
    "build:tutorial": "yarn workspace gatsby-starter-monorepo-tutorial build",
    "serve:tutorial": "yarn workspace gatsby-starter-monorepo-tutorial serve",
    "clean:tutorial": "yarn workspace gatsby-starter-monorepo-tutorial clean",
    "test:tutorial": "start-server-and-test develop:tutorial http://localhost:8000 cy:open",
    "cy:open": "cypress open",
    "cy:run": "cypress run"
  }
}

If you are having any issues you can run yarn workspaces info and you should see something like this if it is properly configured:

{
  "gatsby-starter-monorepo-tutorial": {
    "location": "starters/gatsby-starter-monorepo-tutorial",
    "workspaceDependencies": [
      ## The starter is properly depending on the theme
      "gatsby-theme-monorepo-tutorial"
    ],
    "mismatchedWorkspaceDependencies": []
  },
  "gatsby-theme-monorepo-tutorial": {
    "location": "themes/gatsby-theme-monorepo-tutorial",
    "workspaceDependencies": [],
    "mismatchedWorkspaceDependencies": []
  }
}

Commit to GitHub

At this point you have a minimal working setup of a Gatsby themes monorepo. Now would be a good time to make a commit and push to GitHub.

Develop for the first time

Run yarn develop:your-theme-name to make sure that the starter is building successfully. So for example if your theme was named gatsby-theme-kickoff your command would be yarn develop:kickoff; this script shortens the longer workspaces syntax. You should see a very simple hello world page if it builds correctly.

Congratulations you now have a working Gatsby themes monorepo! But wait there is more!

Setup Lerna

Run lerna init to set Lerna up to monitor your packages for changes. You can use the command lerna publish to publish packages to NPM (you need an account). Before you deploy to a host provider the theme will need to be published.

Lerna watches your packages in the background and tracks whether a particular package needs to be published again or not based on changes. It also automatically manages updating versions for all your packages during the publish process. For a smaller repo you could manage this manually yourself however as my monorepo got larger and more inter-connected Lerna has been a huge productivity win.

Note that any packages with private: true, like your starter, will not publish. Also the publish command will give you a final check before actually publishing anything, so don’t worry you can run lerna publish and then decide not to publish.

The lerna config is located in the lerna.json file and looks like this:

{
  "packages": ["starters/*", "themes/*"],
  "npmClient": "yarn",
  "useWorkspaces": true,
  "version": "independent"
}

Cypress.io and GitHub actions

Run yarn test:your-theme-name to start Cypress.io. So for example if your theme was named gatsby-theme-kickoff your command would be yarn test:kickoff.

Cypress.io is provides excellent, and well documented, end-to-end integration testing for web development. They also maintain a GitHub Action for Cypress.io which allows you to run tests automatically against every PR and Push on the main branch. This CI testing has been tremendously helpful for me in catching errors and preventing merges that contain breaking changes.

The template repository includes a basic smoke test - which simply test whether your site built successfully or not. This smoke test is setup to run against every PR and Push to the main branch of your repo.

There is also a set of tests for accessibilty (a11y) that use cypress-axe to perform a basic accessibility audit of your website. This test needs to be manually run and is not run on every PR and Push although you could configure it to do this.

Here is what a basic Cypress test looks like:

describe('Smoke Test', () => {
  it('Site loads', () => {
    cy.visit(`/`).assertRoute(`/`)
  })
})

Here is what the GitHub action looks like:

name: Test Themes
on:
  push:
    branches:
      - main
    paths:
      - 'starters/**'
      - 'themes/**'
      - '.github/**'
      - 'cypress/**'
  pull_request:
    branches:
      - main
    paths:
      - 'starters/**'
      - 'themes/**'
      - '.github/**'
      - 'cypress/**'
jobs:
  test_theme:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Run Tests
        uses: cypress-io/github-action@v1
        with:
          build: yarn build:empty
          start: yarn serve:empty
          config-file: cypress/cypress-github-actions.json
          spec: cypress/e2e/smoke.test.js

Deploy to Netlify

You don’t have to choose Netlify, you could use any hosting provider. Your theme also needs to be published on NPM for this to work.

Login to Netlify and create a new site with default settings pointed at your new GitHub repo - it will just work which is pretty amazing!

The template monorepo is preconfigured for Netlify using netlify.toml to build and deploy the starter in your starters folder. Netlify will watch the repo and re-deploy when any changes are pushed to the default branch.

Dependency management with Dependabot

Note that this is referring to Dependabot v2.0 which is an internal GitHub process and not the older version which was accessed via dependabot.com

Dependabot is setup and preconfigured via .github/dependabot.yml which automatically activates dependabot for your repo. No sign up, sign in or configuration necessary. Dependabot automates dependency management for your monorepo and will make regular PRs against your default branch with updates.

Here is what the included dependabot config looks like:

version: 2
updates:
  - package-ecosystem: 'npm'
    directory: '/'
    schedule:
      interval: 'daily'
    versioning-strategy: increase
    open-pull-requests-limit: 25
    labels:
      - 'dependencies'
    ignore:
      - dependency-name: 'gatsby'

The included config is set to check for daily updates against your root package.json folder, which because of workspaces will also target all of your sub-folders and dependencies. I have found it helpful to manually manage updates to Gatsby itself so that dependency is being ignored. There is good documentation for these options and other available settings on the GitHub website.

Dependency management is something that in a smaller, and less complicated repo you could probably manage yourself and however on more complicated projects having the dependencies managed automatically is a great feature to have. The other big bonus is that your Cypress.io tests will run against every single PR and allow you to confidently merge updated dependencies after your tests have passed!

End result and next steps

You now have a Gatsby themes monorepo setup with automated testing, automated dependency management, automated publishing, continuous integration, and continuous deployment! Amazing!

One final integration you may want to consider, depending on how you will be using Gatsby themes, is adding a GitHub action to publish your starters to their own repo. Check out actions-push-subdirectory for instructions on how to do this. This is necessary for starters to work properly with the gatsby new command as they need to be in an indepedent repo at the root level.

If you want to see a more advanced version of this monorepo setup you can checkout Gatsby Theme Catalyst which uses these same tools across a repository with over 10 interlinked themes.

Happy coding!