Deploying to GitHub Pages Guide
Reading Time
Reading time for this document is 2 minutes for 429 words.
This site is hosted for free on GitHub Pages and published by GitHub Actions on every push to main. This page explains the setup so you can reproduce it for your own VitePress site.
One-time setup
Name the repository
<owner>.github.io(here,binarynoir.github.io). A repository with this name is published athttps://<owner>.github.io/with no sub-path. Any other repository name publishes athttps://<owner>.github.io/<repo>/, and you must then setbase: '/<repo>/'in the VitePress config.In the repository, open Settings → Pages and set Source to GitHub Actions. Or from the command line:
shgh api repos/<owner>/<repo>/pages -X POST -f build_type=workflowPush to
main. The workflow builds and deploys.
GitHub Pages on a free plan requires a public repository.
The workflow
name: Deploy site to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
# Pages deploys need these three, nothing else.
permissions:
contents: read
pages: write
id-token: write
# One deploy at a time. Let a running deploy finish rather than cancel it
# halfway, but drop queued runs in favor of the newest commit.
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# Full history so VitePress `lastUpdated` timestamps are correct.
fetch-depth: 0
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- uses: actions/configure-pages@v6
- run: npm ci
- run: npm run format:check
- run: npm run docs:build
- uses: actions/upload-pages-artifact@v5
with:
path: docs/.vitepress/dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5A few choices worth knowing about:
- Least privilege. The workflow asks only for
contents: read,pages: writeandid-token: write. The last one lets the deploy step prove its identity through OIDC. There are no tokens or secrets to manage. fetch-depth: 0. VitePress'slastUpdatedreads each page's git history. A shallow clone would stamp every page with the same date.concurrencywithcancel-in-progress: false. Only one deploy runs at a time, and a deploy that is already running is allowed to finish rather than being cut off halfway.npm ciinstalls exactly whatpackage-lock.jsonsays, so the build matches what you tested locally.- Format check before build. A formatting slip fails the run before anything is published.
- Two jobs.
buildproduces an artifact;deploypublishes it, in thegithub-pagesenvironment, which is what gives the run a visible deployment URL.
Pull requests
ci.yml runs the same checks and build on pull requests but never deploys, so a broken change is caught before it reaches main.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Deploy job fails with a Pages 404 or "not enabled" error | Pages Source is not set to GitHub Actions. |
| Site loads but styles and scripts 404 | base does not match the repository name (project sites only). |
| Every page shows the same "Last updated" date | The checkout is shallow. Keep fetch-depth: 0. |
| Build log shows a missing image warning | Image Fallback swapped in a placeholder. On this site that is the intentional demo. |
npm ci fails on a lockfile mismatch | Run npm install locally and commit the updated package-lock.json. |
Custom domain
Add a docs/public/CNAME file containing your domain, set the same domain under Settings → Pages, and point a DNS record at GitHub. VitePress copies public/ into the build output, so the file travels with every deploy.