Listen to Episode 33: Publishing with GitHub Pages - a conversational audio overview of this chapter. Listen before reading to preview the concepts, or after to reinforce what you learned.
GitHub Pages lets you publish a static website straight from a GitHub repository - no server, no hosting bill, no deployment pipeline required for simple sites. This appendix explains how to enable it, what it can publish, and how to ensure the published site meets the same accessibility standards as your source code.
- What GitHub Pages Is
- Enabling GitHub Pages for a Repository
- Publishing Sources
- The html/ Folder in This Project
- Custom Domains
- HTTPS and Security
- Accessibility Considerations for Published Sites
- GitHub Actions and Continuous Deployment
- Limitations
- Troubleshooting
GitHub Pages is a static site hosting service built into GitHub. It serves files directly from a branch or folder in your repository at a URL of the form:
https://<username>.github.io/<repository-name>/
For organization accounts and user profile repositories (<username>/<username>.github.io), the URL becomes:
https://<username>.github.io/
What "static" means: GitHub Pages only serves files as-is - HTML, CSS, JavaScript, images, PDFs. It does not run server-side code (no PHP, no Python, no Node.js request handlers). If you need a database or dynamic server logic, you need a different host.
- Documentation sites
- Workshop materials (like this project)
- Project landing pages
- Personal portfolios
- Simple blogs via Jekyll
- Go to the repository on GitHub.com
- Click Settings (the gear icon in the top navigation)
- In the left sidebar, scroll to Code and automation and click Pages
- Under Build and deployment, choose your publishing source:
- Deploy from a branch - serve files directly from a branch/folder
- GitHub Actions - use a workflow to build and deploy
- If using "Deploy from a branch":
- Select the branch (e.g.
mainormaster) - Select the folder:
/(root) or/docs
- Select the branch (e.g.
- Click Save
GitHub will build and deploy within a minute or two. The URL appears at the top of the Pages settings once the first deployment succeeds.
- The Settings tab is a link in the repository's top navigation bar. It has the accessible name "Settings"
- The Pages option in the left sidebar is a link under the "Code and automation" group heading
- The branch and folder dropdowns are standard
<select>elements - navigate with arrow keys
The simplest option. GitHub reads files directly from a branch.
| Folder option | What it serves |
|---|---|
/ (root) |
Serves the entire repository root |
/docs |
Serves only the docs/ folder - useful when your repository also contains source code |
Best practice: Use /docs to isolate the published content from source files, especially for projects with build pipelines where the output lives in a specific folder.
Use a workflow (YAML file in .github/workflows/) to build your site before publishing. This is required when:
- Your source is Markdown and you need a build step (e.g. this project's
scripts/build-html.js) - You are using a static site generator like Jekyll, Hugo, or Eleventy
- You want to run accessibility tests in CI before publishing
A basic workflow for this project would:
- Check out the repository
- Run
node scripts/build-html.js - Upload the
html/folder as the Pages artifact
This project has a pre-built HTML mirror of all Markdown content in the html/ folder, generated by scripts/build-html.js. To publish this as a GitHub Pages site:
Because GitHub Pages only supports / (root) or /docs as folder sources, and this project's output is in html/, you have two options:
# Copy the html/ output into docs/ for GitHub Pages
cp -r html/* docs/
git add docs/
git commit -m "Publish HTML output to docs/ for GitHub Pages"
git pushThen set Pages source to branch master, folder /docs.
If the project does not already use docs/ for Markdown sources, you could rename the output folder:
# Update the build script output path first, then
git mv html/ docs/
git commit -m "Rename html/ to docs/ for GitHub Pages compatibility"
git pushNote: This project uses docs/ for Markdown source files, so this would conflict. Option A1 or the Actions approach below is safer.
Create .github/workflows/pages.yml:
name: Deploy to GitHub Pages
on:
push:
branches: [master]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Build HTML
run: node scripts/build-html.js
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: html/
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4This workflow triggers on every push to master, rebuilds the HTML, and deploys the html/ folder.
GitHub Pages supports custom domains (e.g. learning.community-access.org instead of community-access.github.io/Learning-Room).
- In repository Settings → Pages, enter your domain in the Custom domain field and click Save
- GitHub creates a
CNAMEfile in the repository root containing your domain name - At your DNS provider, create the appropriate records:
| Domain type | DNS record type | Value |
|---|---|---|
Subdomain (e.g. learning.community-access.org) |
CNAME | community-access.github.io |
Apex domain (e.g. community-access.org) |
A records (×4) | GitHub's IP addresses (see below) |
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
DNS changes can take up to 48 hours to propagate. GitHub Pages checks and verifies the domain automatically once DNS is configured.
To prevent domain takeover attacks, GitHub recommends verifying your custom domain in your account or organization settings (Settings → Pages → Add a domain). This prevents others from claiming your domain for their GitHub Pages if you temporarily remove it.
GitHub Pages enforces HTTPS automatically for github.io subdomains. For custom domains:
- After DNS propagates, check Enforce HTTPS in Pages settings
- GitHub Pages uses Let's Encrypt to provision a certificate automatically
- Enforcing HTTPS redirects all HTTP traffic to HTTPS
Important: Never store secrets, API keys, or private data in a GitHub Pages repository. The repository content is public (on public repositories) and is served as-is. Even deleted files remain in git history.
Publishing a site does not automatically make it accessible. Consider the following for the published HTML output:
The HTML generated by scripts/build-html.js converts Markdown headings and structure to HTML. Verify the output includes:
- A
<main>landmark wrapping the primary content - Logical heading hierarchy (H1 → H2 → H3)
- A page
<title>that reflects the document topic
For sites with repeated navigation on every page, add a skip link as the first element in <body>:
<a class="skip-link" href="#main-content">Skip to main content</a>With CSS to show it only on focus:
.skip-link {
position: absolute;
top: -40px;
left: 0;
}
.skip-link:focus {
top: 0;
}If the site uses JavaScript to load content without full page reloads, manage focus explicitly when content changes. Move focus to the new <h1> or a wrapper with tabindex="-1" after navigation.
All images in published pages should have appropriate alt attributes. Decorative images should use alt="" (empty, not missing) to be skipped by screen readers.
Run the published pages through an automated checker (axe, WAVE) after changes to the stylesheet to verify contrast ratios remain compliant.
After deployment, test the live URL rather than only local files. Some issues (e.g. mixed content, broken relative links, missing files) only appear when served from a web server.
- Navigate all pages keyboard-only
- Run axe DevTools on the index page and at least one content page
- Verify no broken links with a link checker (e.g. W3C Link Checker)
- Test with a screen reader announcement of the page title and
<h1>
Using the Actions workflow described in Section 4 means every push to master automatically rebuilds and redeploys the site.
Extend the workflow to fail the build if accessibility violations are found:
- name: Install axe CLI
run: npm install -g @axe-core/cli
- name: Run accessibility check
run: axe html/index.html --exitThis uses the axe-core CLI to scan the built index page. The --exit flag causes the step to fail if violations are found, blocking the deployment.
For more comprehensive checking, scan multiple pages:
- name: Run accessibility checks
run: |
for file in html/docs/*.html; do
axe "$file" --exit
done| Limitation | Detail |
|---|---|
| Repository size | GitHub Pages sites should be under 1 GB |
| Bandwidth | Soft limit of 100 GB per month |
| Build time | Jekyll builds must complete within 10 minutes |
| No server-side code | No PHP, Python, Ruby on Rails, Node.js handlers |
| No .htaccess | Apache-style redirects are not supported; use _redirects for some static hosts |
| Public repositories | For free accounts, GitHub Pages requires the repository to be public |
| Private Pages | Available on GitHub Enterprise plans only |
- Check the Actions tab for a failed deploy workflow
- Check Settings → Pages - the most recent deployment timestamp should match your push
- Hard-refresh the browser (
Ctrl+F5/Cmd+Shift+R) to bypass the cache - DNS TTL caching can delay custom domain updates up to the TTL value (often 1 hour)
This usually indicates a base URL mismatch. If your site is at https://user.github.io/my-repo/, all relative asset and link paths must account for the /my-repo/ path prefix. Check that the HTML output uses relative paths (./styles/main.css) rather than root-relative paths (/styles/main.css).
- Verify DNS records are correctly set
- Ensure the domain is not proxied through a CDN (e.g. Cloudflare orange-cloud) - GitHub Pages needs to see the DNS record directly to provision the cert
- Allow up to 24 hours after correct DNS propagation
The published <title> element is set during the HTML build step. Update the template in scripts/build-html.js to ensure each page has a unique, descriptive title.
Return to: Resources | Appendix E - GitHub Flavored Markdown | Appendix A - Glossary