Skip to content

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 ​

  1. Name the repository <owner>.github.io (here, binarynoir.github.io). A repository with this name is published at https://<owner>.github.io/ with no sub-path. Any other repository name publishes at https://<owner>.github.io/<repo>/, and you must then set base: '/<repo>/' in the VitePress config.

  2. In the repository, open Settings → Pages and set Source to GitHub Actions. Or from the command line:

    sh
    gh api repos/<owner>/<repo>/pages -X POST -f build_type=workflow
  3. Push to main. The workflow builds and deploys.

GitHub Pages on a free plan requires a public repository.

The workflow ​

yaml
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@v5

A few choices worth knowing about:

  • Least privilege. The workflow asks only for contents: read, pages: write and id-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's lastUpdated reads each page's git history. A shallow clone would stamp every page with the same date.
  • concurrency with cancel-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 ci installs exactly what package-lock.json says, so the build matches what you tested locally.
  • Format check before build. A formatting slip fails the run before anything is published.
  • Two jobs. build produces an artifact; deploy publishes it, in the github-pages environment, 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 ​

SymptomLikely cause
Deploy job fails with a Pages 404 or "not enabled" errorPages Source is not set to GitHub Actions.
Site loads but styles and scripts 404base does not match the repository name (project sites only).
Every page shows the same "Last updated" dateThe checkout is shallow. Keep fetch-depth: 0.
Build log shows a missing image warningImage Fallback swapped in a placeholder. On this site that is the intentional demo.
npm ci fails on a lockfile mismatchRun 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.

Code released under the MIT License. BinaryNoir name and logos are trademarks of BinaryNoir. Legal · Terms · Privacy