TIL: Deploying to GitHub pages

2023-08-15

GitHub pages is a really nice static site option for small projects. It took me some time to get this right, but here’s a little script that can deploy a project easily from my laptop:

#!/usr/bin/env bash

# `x` prints every command the script runs.
set -euox pipefail

if ! git diff; then
  printf "Please clean up your git state as this script needs to checkout the gh-pages branch.\n"
  exit 1
fi

TEMP_DIR=$(mktemp -d)

# Defer cleanup.
cleanup() {
  ls "$TEMP_DIR"
  rm -rf "$TEMP_DIR"
}
trap cleanup EXIT

# Clear previous `dist`.
if [ -d dist ]; then
  rm -rf dist
fi

# Writes new files to `dist`.
pnpm build
cp -r dist/* "$TEMP_DIR"

# Checkout `gh-pages` if it exists. Might not need this `if` check
if git show-ref --quiet refs/heads/gh-pages; then
  git checkout gh-pages
else
  git checkout --orphan gh-pages
fi

# Remove all git tracked files.
git rm -rf .
git checkout main -- .gitignore

# Copy over the new assets.
cp -r "$TEMP_DIR"/* .

# Add, commit, push.
git add -A
git commit -m "gh-pages deploy"
git push

# Return to the main branch.
git checkout main

Briefly, it:

  1. Ensures your git diff is clear since it needs to checkout the gh-pages branch later
  2. Clears out any previous dist directory
  3. Runs pnpm build and copies all the output to a temporary dir
  4. Checks out the orphan gh-pages branch. An orphaned branch has its own separate history from the other branches which is helpful for cases like GH pages.
  5. Deletes all the files normally tracked by git
  6. Restores the .gitignore since that’s still useful here
  7. Copies over the built files from the temporary directory
  8. Stages all the built files and commits the built files
  9. Pushes the commit
  10. Checks out the main branch again to restore the previous state

Using Parcel? Make sure you add this to your package.json that matches your GH repo name:

  "targets": {
    "default": {
      "publicUrl": "/my-repo-name"
    }
  },

Using Astro? Be sure to define site and base in your astro.config.mjs:

export default defineConfig({
  site: "https://jondlm.github.io/website/",
  base: "website",
});