A custom GitHub Actions workflow for static HTML pages

I build files for this website locally and push the static HTML files to GitHub Pages, using the default workflow. At some point, I started noticing that the deploys were taking long to complete. A deploy from today shows that it took 40 seconds to render.

I decided to looked into the workflow itself using GitHub’s command line tool, and found a job called build, which takes the most time to complete.

$ gh run list --limit 1
STATUS  TITLE              WORKFLOW        BRANCH  EVENT    ID           ELAPSED  AGE
✓       pages build an...  pages-build...  main    dynamic  19118060009  40s      about 50 mi...
$ gh run view 19118060009

✓ main pages-build-deployment · 19118060009
Triggered via dynamic about 50 minutes ago

JOBS
✓ build in 22s (ID 54632038648)
✓ deploy in 8s (ID 54632067818)
✓ report-build-status in 5s (ID 54632067849)
...

What does build do? It builds the site with Jekyll.

$ gh run view --job=54632038648

✓ main pages-build-deployment · 19118060009
Triggered via dynamic about 50 minutes ago

✓ build in 22s (ID 54632038648)
  ✓ Set up job
  ✓ Pull ghcr.io/actions/jekyll-build-pages:v1.0.13
  ✓ Checkout
  ✓ Build with Jekyll
  ✓ Upload artifact
  ✓ Post Checkout
  ✓ Complete job

My website doesn’t need to be built by Jekyll, and GitHub provides a solution to skip it—to add a file called .nojekyll to the root of the repository.

So I added the file and pushed the changes, and the workflow ran again. This time it took 20 seconds to run, half the time from the last workflow! However, the build step is still there.

$ gh run list --limit 1
STATUS  TITLE              WORKFLOW        BRANCH  EVENT    ID           ELAPSED  AGE
✓       pages build an...  pages-build...  main    dynamic  19118930798  20s      about 9 min...
$ gh run view 19118930798

✓ main pages-build-deployment · 19118930798
Triggered via dynamic about 9 minutes ago

JOBS
✓ build in 4s (ID 54634839639)
✓ report-build-status in 3s (ID 54634846786)
✓ deploy in 8s (ID 54634846814)
...

I was under the assumption that .nojekyll would remove the build job, but that wasn’t the case. Instead, it only removes one of the steps in the job, that runs Jekyll.

$ gh run view --job=54634839639

✓ main pages-build-deployment · 19118930798
Triggered via dynamic about 9 minutes ago

✓ build in 4s (ID 54634839639)
  ✓ Set up job
  ✓ Checkout
  ✓ Upload artifact
  ✓ Post Checkout
  ✓ Complete job
...

It feels unnecessary to have to run the build and report-build-status jobs each time, when all I really need is just the deploy job. I wanted to speed this up further.

After some clickity-clakity on the Pages section of the repository settings page, I discovered two options for deployment. One is to deploy from a branch, which is the default GitHub Pages workflow. The other is GitHub Actions, which allows for custom workflows.

After clicking on GitHub Actions option, the displays some suggested templates. One of those templates is Static HTML, which sounds like exactly what I’m looking for! You can view the full template on the starter-workflows repository on GitHub, but here is an excerpt:

jobs:
  # Single deploy job since we're just deploying
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup Pages
        uses: actions/configure-pages@v5
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          # Upload entire repository
          path: '.'
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

In comparison to the GitHub Pages workflow which had three jobs, this has only one - deploy - exactly what I needed! I took the template as is and added it to my repository at .github/workflows/. One thing I changed was commenting out the Setup Pages step, as I didn’t recall seeing that step in the previous workflows.

I expected a much bigger speedup for the custom workflow run, but it was only a second faster.

$ gh run list --limit 1
✓       *                  Deploy stat...  main    push     19120221474  18s      about 15 mi...
$ gh run view 19120221474

✓ main Deploy static content to Pages · 19120221474
Triggered via push about 15 minutes ago

JOBS
✓ deploy in 14s (ID 54638891569)
...

Looking closely at the steps, I get a better sense why. The deploy job is now a mix of the build and deploy jobs from the second workflow run (after adding .nojekyll). The number of jobs may have reduced from three to one, but the number of steps are more or less the same.

Combining all steps into one job does reduce some overhead. The original workflow ran a setup and cleanup for each of the three jobs. With one job, the setup and cleanup happens only once, which saves a second or two.

I’ll take a few seconds of speedup as a win.