Tighten's Jigsaw, GitHub Pages and GitHub Actions cover image

January 1, 2020

Tighten's Jigsaw, GitHub Pages and GitHub Actions


This blog is powered by Tighten's Jigsaw project, which if you're thinking about starting your own blog, I can highly recommend!

Since Jigsaw generates a static website, I chose to host it with GitHub Pages as a "user site" as they allow for top-level domains. GitHub Pages only serve static sites, but as Jigsaw is a dynamic system we need to generate the site. I was able to automate this process with GitHub Actions. It took some trial and error (read: a lot of wip commits) but I was finally able to make it work and wanted to share this setup as I think it's pretty sweet.

I'll break this down into two sections, the first, using Jigsaw and GitHub Pages and the second, publishing with GitHub Actions.

Jigsaw & GitHub Pages

Jigsaw provides instructions on how to use GitHub Pages, but it's written for project sites and not user sites. If you're not sure why this is a problem, it's because user sites require that the site contents are in the master branch, whereas repository sites default to gh-pages - though that can be changed.

To get this working, I setup Jigsaw under a source branch which contains all of the code, assets and post content. I then push the contents of the build_production directory to the master branch, which GitHub happily serves up for you to read.

It's a small change, but it may not immediately be obvious, so it's worth clarifying!

Jigsaw & GitHub Actions

Jigsaw is really, really good but I'm lazy and don't want to manually be building my website, committing and publishing it after each post.

Admittedly, it took me a lot of time to figure these steps out, but now I'm able to write a new post and have GitHub automatically build and publish it for me!

I created a new workflow under .github/workflows/build-publish.yml with the following:

1name: Build & Publish
4 push:
5 branches:
6 - source
7 schedule:
8 - cron: "0 2 * * 1-5"
11 build-site:
12 runs-on: ubuntu-latest
13 steps:
14 - uses: actions/[email protected]
15 - name: Install Composer Dependencies
16 run: composer install --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
17 - name: Install NPM Dependencies
18 run: npm install
19 - name: Build Site
20 run: npm run production
21 - name: Create CNAME File
22 run: echo "james.brooks.page" >> build_production/CNAME
23 - name: Stage Files
24 run: git add -f build_production
25 - name: Commit files
26 run: |
27 git config --local user.email "[email protected]"
28 git config --local user.name "GitHub Actions"
29 git commit -m "Build for deploy"
30 - name: Publish
31 run: |
32 git subtree split --prefix build_production -b master
33 git push -f origin master:master
34 git branch -D master

It may look complicated, but we can break it down.

  1. We tell GitHub to only run the workflow on pushes to the source branch. I also opted to automatically run the workflow at 2PM Monday - Friday, just in case 🤷🏻‍♂️
  2. Next we're installing the Composer and NPM dependencies.
  3. Once the dependencies have been installed, we build the site using the production environment configuration.
  4. Optional! I'm using a CNAME on GitHub Pages so that I can use my own domain. I create a new file and fill it with the domain name I want to use.
  5. I forcefully stage the build_production directory, this is because Jigsaw defaults to ignoring build_* directories from Git and I don't want to change this and accidentally publish it into the source branch.
  6. Next we configure Git's email and name, I chose to use GitHub Actions as the author so that I know it's an automatic commit. We then commit the contents with a pre-defined commit message.
  7. Finally, we're going to publish the site by pushing the changes from within the GitHub Action itself! This is a bit complicated if you're not confident with Git, but let's try to keep it simple. We create a subtree of the build_production directory and pushing it into a temporary master branch. Now we can force push the branch to GitHub and clean up by deleting the master branch1.

And that's it, I'm now able to write new blog posts without my laptop (using just the GitHub website) and have them immediately published for me.

Let me know @jbrooksuk if you're using this technique or have any suggestions on how to improve it.


  1. Because we're force-pushing to master from a branch which contains only one commit, all history is lost for the published website. This is a trade-off that I'm happy with because the real history is in the source directory.