GitHub Pages
Free for public repositories, HTTPS and custom domains included, and the CI is already where your code is. The demo apps are deployed this way.
Enable Pages
Settings → Pages → Build and deployment → Source: GitHub Actions.
Not "Deploy from a branch". The workflow below uploads an artifact and deploys it directly; the branch-based source would ignore it.
A workflow
name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy
cancel-in-progress: false
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-24.04
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- run: npm ci
- name: Apply customization
run: npx engraft apply --template engraft.template.yml --values customization/my-museum/engraft.variables.yml
- name: Build
run: npm run build
- uses: actions/upload-pages-artifact@v5
with:
path: www
- uses: actions/deploy-pages@v5
id: deployment
Three things to note:
concurrencywithoutcancel-in-progress. Two deploys racing each other fight over the Pages environment, and cancelling a half-finished one can leave the site mid-swap.- The engraft step brands the build. Without it you deploy the Animals demo.
- Offline support comes from
offline_support: "true"in your values file. See Offline.
Your site appears at https://<account>.github.io/<repository>/.
Serving from a subdirectory
That URL is a subdirectory, so unless you attach a custom domain, add the two path fixes:
- name: Build
run: |
npm run build
sed -i 's|"/build/|"build/|g' www/index.html
sed -i "s|'/sw.js'|'/<repository>/sw.js'|" www/index.html
With a custom domain the app sits at the root and neither line is needed.
Several variants in one repository
The app's own release workflow builds two guides and deploys both:
# Default variant
- run: npm run build
- run: |
sed -i 's|"/build/|"build/|g' www/index.html
sed -i "s|'/sw.js'|'/audioguide-app/animals/sw.js'|" www/index.html
- run: mv www animals
# Second variant
- run: npx engraft apply --template engraft.template.yml --values customization/leon/engraft.variables.yml
- run: npm run build
- run: |
sed -i 's|"/build/|"build/|g' www/index.html
sed -i "s|'/sw.js'|'/audioguide-app/leon/sw.js'|" www/index.html
- run: mv www leon
- run: |
mkdir _site
mv animals _site/animals
mv leon _site/leon
- uses: actions/upload-pages-artifact@v5
with:
path: _site
Each variant gets its own directory, service-worker path and URL. Mind that applies are not additive — see Variants.
Custom domain
1. DNS
A subdomain (recommended) — a CNAME record pointing at <account>.github.io.:
guide.museum.org. CNAME <account>.github.io.
An apex domain — four A records, because an apex cannot be a CNAME:
museum.org. A 185.199.108.153
museum.org. A 185.199.109.153
museum.org. A 185.199.110.153
museum.org. A 185.199.111.153
Add AAAA records for IPv6. GitHub's current addresses are in their documentation — check there rather than trusting this page.
A subdomain is easier to set up, easier to move, and does not entangle your main website's DNS with a static-site host.
2. Tell GitHub
Settings → Pages → Custom domain, enter it, save. Wait for the DNS check to pass, then tick Enforce HTTPS. The certificate takes a few minutes to issue.
3. Keep the CNAME file
GitHub needs a CNAME file in the deployed site to keep the domain. A workflow that uploads www/ does not include it, and the domain is dropped on the next deploy. Write it into the artifact, after the build and before the upload:
- run: echo "guide.museum.org" > www/CNAME
Private repositories
GitHub Pages on a private repository needs a paid GitHub plan. On the free plan the repository has to be public for Pages to serve it. If that is a problem, Netlify serves private repositories on its free tier.
Limits
| Repository size | 1 GB recommended |
| Published site | 1 GB |
| Bandwidth | 100 GB/month, soft limit |
| Builds | 10 per hour, soft limit |
The app itself is a few megabytes. The number to watch is bandwidth, and only if your media is on Pages too: a 60 MB offline guide downloaded by two thousand visitors is 120 GB. A large or busy guide belongs on object storage or behind a CDN. See Media.
Troubleshooting
404 after a successful deploy
The Pages source is still "Deploy from a branch". Change it to GitHub Actions.
The page loads but stays blank
The subdirectory path fixes are missing. Apply the two sed lines above.
The custom domain disappears after every deploy
The CNAME file is not in the uploaded artifact. Add the echo step.
"Enforce HTTPS" is greyed out
The certificate has not been issued yet. Usually minutes, occasionally up to an hour after DNS propagates.
The service worker returns 404
Its registration path still points at the root. Apply the sed fix, and clear the site data on your test device — an old worker keeps serving the old paths.