Reimaging the Interlisp.github.io Staging Site #2704
Replies: 1 comment
|
2026-08-17 Update: The staging site has been built and tested. You can see a first example of what it looks like at: https://interlisp.github.io/Interlisp.staging/pr-342/ To date we have created a workflow repository that contains the shared build workflow used both by the production and staging sites (shared-workflows), created the staging site (Interlisp-staging), deployed the needed workflows in the staging repository, created the mechanism to allow communication between Interlisp.org repositories. The staging workflow exercises the communication token and writes a comment into the PR with the staging site path. You can view this last part in the PR comments for PR-342. The last remaining steps are end-to-end testing and also ensuring that the site teardown workflows work correctly when a PR is merged. |
Uh oh!
There was an error while loading. Please reload this page.
This is a followup from our discussion at the Wednesday, July 22, External Development meeting. I had started exploring using Cloudflare to build staging sites based on individual PRs.
There was much discussion on Cloudflare and some other ideas were shared on potential build processes for a staging site. Since then, I have done some additional research. I started writing a review of my findings and, in the process, used AI to add additional detail (and draw some pretty pictures). The following, in my estimate, provides an accurate portrayal of the 3 approaches I'm aware of along with the pros and cons of each approach.
My personal preference is to go with Option 2 - creating a second staging repository within the Interlisp GitHub site.
The staging site for Interlisp.org currently resides in a private repo and deployments to it are dependent on one individual. We need a process that is automated and does not have a single point of failure.
The staging site has been supported for several years and has been a useful tool. It has allowed prototypes of features to be exercised and evaluated prior to introduction into our production branch. However, not every feature makes it to the staging site and the process is ad hoc.
A better process would be to automatically deploy each PR to a temporary staging site where it can be viewed and discussed prior to the PR being merged into the main branch.
Our deployment process is to use a GitHub Action to build and deploy the website as an org site and map that to our domain, Interlisp.org.
There are three approaches that warrant consideration. The first and third approaches are ones I knew about when I created the initial staging site. I wasn't aware of the second approach as an option at that point in time. I don't believe the first approach is a viable alternative, but I left in and expanded upon it primarily as background and setting the stage for the two other alternatives.
1. Deploy the staging branch within the existing repository
This approach, while the simplest sounding, is fundamentally incompatible with how GitHub Pages serves organization sites.
Org site vs. project site distinction. GitHub Pages has two modes: user/org sites (
<org>.github.io) and project sites (<org>.github.io/<repo>). An org site is the account-level website — there is exactly one per GitHub organization, published from a single branch of the single repository named<org>.github.io. There is no path-prefix mechanism to host multiple sites at different URLs from the same repo. Project sites avoid this because each lives under its own path (/<repo-name>), but the custom domaininterlisp.orgis bound to the org site root, so there is no such path prefix to leverage.Custom domain limitation. GitHub Pages allows only one custom domain per repository. Since
interlisp.orgis already mapped to this repo, you cannot configure a second domain likestaging.interlisp.orgfrom the same repository — even from a different branch. TheCNAMEfile at the repository root is a single setting; whichever branch is published, itsCNAME(or lack thereof) controls the domain for the entire deployment.No per-branch deployment isolation. When GitHub Pages publishes a branch in an org site repo, it publishes to the same single URL. There is no routing layer that could serve one branch at
interlisp.organd another atstaging.interlisp.org. The deployment slot is singular, and theactions/deploy-pagesaction used in the current workflow targets that single slot. GitHub Pages' "deployment previews" feature — where each PR gets a unique URL — exists only for project sites, not for org/user sites.Subdirectory workarounds are fragile. One might try building a staging branch's output into a subdirectory of the production branch (e.g.,
interlisp.org/staging/). This would require:/staging/pathIn short, GitHub Pages does not support branch-based or PR-based staging for organization sites at the account level, making this approach nonviable without either (a) dropping the custom domain for staging, (b) accepting that staging deploys overwrite the production site, or (c) introducing a fragile subdirectory scheme with no isolation.
Pros:
Cons:
2. Create a second repository within the Interlisp GitHub account
A separate repo (e.g.,
interlisp-staging) with its own GitHub Pages deployment. Unlike the org site, the staging repo is a project site (https://interlisp.github.io/interlisp-staging/), which enables per-PR preview URLs via subdirectory deployments. Conceptually, this repo acts purely as a deployment target — previews are pushed to it but approval and merge still happen in the primary repo.Architecture overview.
Shared build pipeline via org-level reusable workflows.
Both the production and staging workflows reference a single reusable workflow stored in an org-level
.githubrepository (Interlisp/.github/.github/workflows/build-site.yml). The double.githubis correct — the repository itself is named.github, and inside it workflows live in the standard.github/workflows/directory. This ensures the same build steps — Zotero bibliography retrieval, Hugo compilation, PostCSS processing — are used everywhere, and any update to the build pipeline takes effect across all repos at once. Note: I left the double.githubnameing in here, but another maybe more palitable option is to create another repo for organization wide workflows. In my day job we have aci-scriptsrepo for shared workflows used across all our CI functions.No duplication of build logic, no risk of drift between environments.
Cross-repo triggering via workflow_dispatch.
The production repo's workflow triggers the staging repo's deployment workflow by sending a
workflow_dispatchevent. This requires a token that crosses repository boundaries — a Personal Access Token (PAT) withpublic_reposcope, or a GitHub App token created viaactions/create-github-app-token@v3, stored as an organization secret so any repo in the org can use it.The default
GITHUB_TOKENcannot trigger workflows in another repository, so an org-level secret is mandatory. Using a GitHub App token is preferred over a PAT because it is short-lived and scoped to specific repositories.Per-PR subdirectory deployment.
The staging repo serves each PR from a unique subdirectory path on its Pages site. This is achieved with the same proven tooling available for same-repo subdirectory approaches:
rossjrw/pr-preview-action— handles the full lifecycle: deploys on PR open/update, posts the preview URL as a PR comment, and removes the subdirectory on PR close.peaceiris/actions-gh-pageswithdestination_dir: pr-${{ github.event.inputs.pr_number }}andkeep_files: true— a lower-level alternative if more control is needed.The resulting URL pattern:
https://interlisp.github.io/interlisp-staging/https://interlisp.github.io/interlisp-staging/pr-123/https://interlisp.github.io/interlisp-staging/pr-456/Optionally, a custom domain like
staging.interlisp.orgcan be added to the staging repo via its ownCNAMEfile (no conflict since it is a separate repo), yielding cleaner URLs likestaging.interlisp.org/pr-123/.Cleanup lifecycle.
When a PR is closed in the production repo, a
workflow_dispatch(or a dedicatedpull_request: [closed]trigger) calls the staging repo to remove the PR's subdirectory. Therossjrw/pr-preview-actionhandles this automatically. Without it, a cleanup step would need to run:Comparison with third-party services (Approach 3).
staging.interlisp.org/pr-123/pr-123--interlisp.netlify.appSummary.
Approach 2 provides complete isolation from the production site while staying within GitHub's ecosystem. It requires more up-front effort than a third-party service — setting up the staging repo, creating an org-level reusable workflow, configuring cross-repo tokens, and managing the preview lifecycle — but it avoids any external dependency. The result is a fully automated per-PR preview system where every pull request gets a unique, shareable URL and is cleaned up automatically when merged or closed.
Pros:
/pr-123/) enable simultaneous review of multiple PRsstaging.interlisp.org) via the separate repo's CNAMECons:
.githubrepository to host the reusable workflowbaseURLmust be configured per preview)repository_dispatch/workflow_dispatchworkflows can only be triggered on the target repo's default branch, adding a constraint on how the staging repo is structured3. Use a third-party service (Netlify or Cloudflare Pages)
Both Netlify and Cloudflare Pages offer native deploy previews — every PR gets a unique, shareable URL automatically, and previews are torn down when the PR is closed. Unlike the cross-repo approach, no secondary repository, cross-repo tokens, or custom lifecycle scripting is needed; the service handles everything.
Architecture overview.
The production site (
interlisp.org) continues to be deployed via the existing GitHub Actions workflow. The third-party service runs alongside it — it is given read access to the repository and automatically builds and deploys previews for each PR. The production pipeline is untouched; the preview pipeline is additive.Setup overview.
InterlispGitHub organizationinterlisp.github.ioNo modifications to the existing GitHub Actions workflow are required. The service's GitHub App manages webhook-based triggers independently.
Netlify vs. Cloudflare Pages comparison.
Both platforms satisfy the core requirement — automatic per-PR previews with unique URLs — but differ significantly in their free tier generosity, GitHub integration depth, and edge network scale.
deploy-preview-123--sitename.netlify.app<hash>.project.pages.devnetlify.tomlfile in repo (build command, per-context settings, headers, redirects)wrangler.toml— no equivalent single-file config for build settingsHUGO_VERSIONenv varHUGO_VERSIONenv var; embedded Dart Sass supportKey differentiators for interlisp.org:
Cloudflare has significantly better free-tier economics. Unlimited bandwidth means no surprise bills if a post about Interlisp goes viral. 500 builds/month is generous. The only notable gap is no built-in PR comments and no fork PR preview support — but both can be worked around with a lightweight GitHub Actions workflow that calls
cloudflare/wrangler-action@v3directly, which also gives back full control over the build process.Netlify has a richer free-tier feature set — automatic PR comments and fork PR previews work out of the box with zero configuration. However, the credit-based pricing (300 credits/month) is constraining. A single production deploy costs 15 credits, and bandwidth is 20 credits/GB, meaning the free tier can be exhausted quickly on a site with real traffic.
Cloudflare's edge network (330+ PoPs) is an order of magnitude larger than Netlify's (~20 PoPs), meaning faster global page loads for visitors worldwide — particularly relevant for a historical computing project with an international audience.
Lock-in considerations.
Both services build from standard static output (HTML, CSS, JS). Hugo produces plain files — migrating between platforms is straightforward: copy the
_headersand_redirectsfiles (which use the same format on both platforms) and point DNS at the new host. Lock-in is minimal unless platform-specific features (Netlify Forms, Cloudflare Workers) are adopted, which this site has no need for.Summary.
Approach 3 requires the least implementation effort and provides the best developer experience for PR review. Setup is measured in minutes, not days. The primary tradeoff is between Netlify's richer free-tier features (auto PR comments, fork PR support) and Cloudflare's dramatically more generous free-tier limits (unlimited bandwidth, 500 builds/month) and superior CDN performance. For an open-source project with unpredictable traffic and external contributors, Cloudflare Pages is the stronger choice despite the minor feature gaps, which can be closed with a small amount of custom workflow code.
Pros:
_headers/_redirectsfilesCons:
All reactions