Deploy previews with GitHub pages

GitHub
learning
deployment
Wouldn’t it be nice if when a PR is created, you automatically got a deployed version of your changes to look at?
Author

Rhian Davies

Published

December 4, 2024

When reviewing a pull request (PR) for a Quarto website, it’s good practice to check the rendered output, as well as the code. This is useful for ensuring that the changes look as expected, for example, ensuring that bullet points have rendered correctly, or that images are well sized.

However, it’s a pain for the reviewer to clone the repository and render the Quarto site locally just to check it looks correct. Wouldn’t it be nice if when the PR was created, you automatically got a deployed version of your changes to look at?

Other development platforms like Netlify and Vercel have offered deploy previews for a while, and although these are free for individual users in public repos they aren’t free for organisations.

There has been discussion of GitHub deploy preview for a few years, but there is currently no ETA for this feature. However, there is a popular GitHub marketplace action deploy-pr-preview by rossjrw which does just what we need.

This features of this action are:

How to use deploy-pr-preview

We weren’t doing any CI/CD on PRs initially, so first I need to define a new workflow. Workflows are defined in .yaml files in the .github/workflows folder. At the top of the workflow, I need to give it a name and tell it when to trigger. In this case I want it to trigger on any PR.

name: Quarto Preview

on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
      - closed

Once I’ve defined when it should run, I need to specify what it should run. That tends to involve a number of steps such as

  • Checking out the repository
  • Installing system dependencies
  • Installing packages (via {renv})
  • Rendering the Quarto site

We already have a publish.yml workflow for main which has a number of relevant steps which I’ve borrowed.

The file looks a bit like this:

jobs:
  build-deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./.github/workflows
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Use cache
        uses: actions/cache@v4
        with:
          key: freeze
          path: _freeze

      - name: Install System Dependencies
        run:  bash install_system_deps.sh

      - name: Set up R
        uses: r-lib/actions/setup-r@v2
        with:
          use-public-rspm: true
      
      - name: Set up renv
        uses: r-lib/actions/setup-renv@v2

      - name: Setup Quarto
        uses: quarto-dev/quarto-actions/setup@v2

      - name: Render
        uses: quarto-dev/quarto-actions/render@v2

Once the site has rendered, we just need to add a step to deploy the PR.

      - name: Deploy PR Preview
        uses: rossjrw/pr-preview-action@v1.4.8
        with:
          source-dir: ./_site/

Now everytime a PR is created, or updated, the GitHub Action bot will spring into action.

A screenshot of a GitHub PR. Under the description, there is a comment from the GitHub actions bot with links to the deployed PR.

Adjusting the publish.yml

That’s all working and looking good! However, there is one last change we need to make. When someone merges into main, the publish action is triggered, and sometimes in that process the pr-previews can get wiped. This means that your preview link would no longer work if someone merges another branch to main whilst you’re working on your PR.

We were using the standard Quarto publish action which as far as I know, does not have an inbuilt configuration to exclude folders from the clean up process.

Instead, I’ve replaced the Quarto action with another marketplace action, github-pages-deploy-action by JamesIves which has an inbuilt way to exclude certain folders from the clean-up. Since it’s not a specific Quarto publish action, we need to remember to add a Quarto render step first.

      - name: Publish to GitHub Pages (and render)
        uses: quarto-dev/quarto-actions/publish@v2
        with:
          target: gh-pages
      - name: Render Quarto
        uses: quarto-dev/quarto-actions/render@v2

      - name: Deploy 🚀
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          folder: _site/
          clean-exclude: pr-preview/
          force: false

Spring cleaning

Now that we have a preview workflow (for branches) and a publish workflow (for main), we have a number of duplicated steps. As any good programmer knows, this is not ideal as it goes against the DRY prinicipal and makes code harder to maintain.

I couldn’t find a quick way for two workflows to share steps1, so as a small improvement, I moved the lengthy system depencies call into it’s own script. I then ran the script using the following snippet. and called it via the following step.

      - name: Install System Dependencies
        run:  bash install_system_deps.sh

which I think is a bit nicer to read than the original.

 - name: Install System Dependencies
        run: |
          sudo apt update
          sudo apt install -y cmake
          sudo apt install -y gdal-bin
          sudo apt install -y git
          sudo apt install -y libcurl4-openssl-dev
          ...

In order for the actions to find the script, I also specified the path when I defined the job.

jobs:
  build-deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./.github/workflows

Now we’ve got this working for GitHub pages 🎉 we’d also like to start using it for some of our deployments on Posit Connect. But that’s for another day.

Footnotes

  1. If you know how to do this, do let me know, or make a PR.↩︎