--- title: How to build and deploy custom GitHub Pages date: "2025-04-01T05:59:18Z" lastmod: "2025-04-01T05:59:20Z" categories: - coding wp_id: 4010 description: "A single-job GitHub Pages workflow is enough for many static deployments, and modern toolchains like uv, node, and deno make lightweight publishing pipelines straightforward." keywords: [GitHub Pages, GitHub Actions, deployment, static sites, uv, node, deno] --- Here's the [GitHub Actions](https://github.com/features/actions) file (`.github/workflows/deploy.yaml`) I use to publish to [GitHub pages](https://pages.github.com/). ```yaml name: Deploy to GitHub Pages on: # Run when pushed. Use { branches: [main, master] } to run only on specific branches push: # Allow manual triggering of the workflow workflow_dispatch: # OPTIONAL: Run at a specific cron schedule, e.g. first day of every month at 12:00 UTC (noon) schedule: - cron: "0 12 1 * *" permissions: # To deploy to GitHub Pages pages: write # To verify that deployment originated from the right source id-token: write jobs: # Run as a single build + deploy job to reduce setup time deploy: # Specify the deployment environment. Displays the URL in the GitHub Actions UI environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} # Run on the latest Ubuntu LTS runs-on: ubuntu-latest \ steps: # Checkout the repository - uses: actions/checkout@v4 # Run whatever commands you want - run: echo '

Hello World

' > index.html # Upload a specific page to GitHub Pages. Defaults to _site - uses: actions/upload-pages-artifact@v3 with: path: . # Deploy the built site to GitHub Pages. The `id:` is required to show the URL in the GitHub Actions UI - id: deployment uses: actions/deploy-pages@v4 ``` This is based on [Simon Willison's workflow](https://til.simonwillison.net/github-actions/github-pages) and some of my earlier actions. This **combines build and deploy** jobs. For simple sites, that's simpler and more efficient. For complex builds with parallel execution or need for better error recovery, multiple jobs will help. I build sites with [uv](https://github.com/astral-sh/uv), [node](https://nodejs.org/), or [deno](https://deno.com/). Here are examples of each A sample [uv-based deployment](https://github.com/sanand0/webfeatures/blob/main/.github/workflows/deploy.yml). ```yaml # Install uv - uses: astral-sh/setup-uv@v5 # Run a Python script - run: uv run scraper.py ``` A sample [node package.json deployment](https://github.com/gramener/gramex-network/blob/main/.github/workflows/deploy.yml) and an [npx deployment](https://github.com/sanand0/llmhallucinations/blob/main/.github/workflows/deploy.yml). ```yaml # Install node - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://npm.pkg.github.com/ # Install and build via package.json - run: npm install - run: npm run build # Or, directly use npx. For example, generate HTML with Marp - run: npx -y @marp-team/marp-cli@latest README.md -o index.html # Update content directly, e.g. add an SVG favicon as a data URL - run: sed -i 's/<\/head>/<\/head>/g' index.html ``` A sample [deno deployment](https://github.com/sanand0/til/blob/main/.github/workflows/deploy.yml). ```yaml # Install deno - uses: denoland/setup-deno@v1 with: deno-version: v1.x # Run a Deno script. Use environment variables if needed - env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: deno run --allow-read --allow-write --allow-env --allow-net script.js ```