GitHub Pages Custom Domain Setup

Configuring a custom domain for GitHub Pages requires coordinating DNS, repo settings, build tool config, and deployment mode — each can silently break the others.

Tags

GitHub Pages Custom Domain Setup

The Lesson

Custom domain setup for GitHub Pages is a four-layer coordination problem: DNS records, repository Pages settings, build tool configuration, and deployment mode. A mistake at any layer produces a 404 with no obvious error message. Understanding which layer controls what prevents the silent failures that make this setup frustrating.

Context

Lessons Hub is a static site built with Astro and deployed to GitHub Pages via GitHub Actions (actions/deploy-pages@v4). The site originally lived at bonjohen.github.io/lessons/ (subpath deployment). Moving it to lessons.johnboen.com (custom subdomain at root) required changes at every layer of the stack.

What Happened

  1. DNS configured correctly. Added a CNAME record: lessons.johnboen.combonjohen.github.io. GitHub's DNS check passed immediately.
  2. Custom domain set in GitHub Settings. Entered lessons.johnboen.com in Settings → Pages → Custom domain. GitHub auto-created a CNAME file at the repository root containing lessons.johnboen.com.
  3. First breakage: all navigation links 404'd. The Astro config still had base: '/lessons', which prefixed every internal link with /lessons/. On the custom domain (serving from /), those paths didn't exist. Fixed by changing base to '/' and site to 'https://lessons.johnboen.com'.
  4. Second breakage: CNAME file conflict. Astro copies public/ into dist/ at build time, so the correct location for the CNAME file is public/CNAME. But GitHub had auto-created a competing CNAME at the repo root. During a merge, the root CNAME was deleted — which also cleared the custom domain setting in GitHub Pages, resetting cname to null.
  5. Third breakage: deployment mode mismatch. After the CNAME was cleared, GitHub Pages reverted to build_type: "legacy" (deploy from branch). The repo uses actions/deploy-pages@v4 (Actions-based deployment), which requires build_type: "workflow". The site built and deployed successfully, but GitHub wasn't serving the artifact.
  6. Resolution via API. Fixed both issues with a single API call:
    gh api -X PUT repos/bonjohen/lessons/pages \
      -f build_type=workflow \
      -f cname=lessons.johnboen.com
    

Key Insights

Examples

Diagnosing a 404

# Check Pages config — look at cname, build_type, and status
gh api repos/bonjohen/lessons/pages

# Response showing the problem:
# { "cname": null, "build_type": "legacy", "status": "built" }
#   ↑ domain cleared    ↑ wrong for Actions deploys

Fixing via API

# Re-set custom domain and correct build type in one call
gh api -X PUT repos/bonjohen/lessons/pages \
  -f build_type=workflow \
  -f cname=lessons.johnboen.com

Astro config for custom domain

// Before (subpath deployment)
export default defineConfig({
  output: 'static',
  site: 'https://bonjohen.github.io',
  base: '/lessons',
});

// After (custom domain at root)
export default defineConfig({
  output: 'static',
  site: 'https://lessons.johnboen.com',
  base: '/',
});

Applicability

This applies to any static site generator (Astro, Next.js, Hugo, Jekyll) deployed to GitHub Pages with a custom domain. The specific file location for CNAME varies by tool (public/CNAME for Astro/Next.js, root CNAME for Jekyll since Jekyll doesn't overwrite it), but the coordination problem is the same. The build_type issue is specific to repos using GitHub Actions deployment rather than branch-based deployment.

Related Lessons

Related Lessons