Appendix P: Publishing with GitHub Pages
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.
How to Deploy a Static Website Directly from Your Repository
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.
Table of Contents
- 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
1. What GitHub Pages Is
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.
What it is good for
- Documentation sites
- Workshop materials (like this project)
- Project landing pages
- Personal portfolios
- Simple blogs via Jekyll
2. Enabling GitHub Pages for a Repository
Step-by-step (GitHub.com)
- 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.
Screen reader navigation for the Pages settings page
- 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
3. Publishing Sources
Deploy from a branch
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.
GitHub Actions
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
4. The html/ Folder in This Project
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:
Option A: Manual publishing from html/ folder
Because GitHub Pages only supports / (root) or /docs as folder sources, and this project's output is in html/, you have two options:
Option A1 - Copy html/ contents to docs/
# 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 push
Then set Pages source to branch master, folder /docs.
Option A2 - Rename html/ to 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 push
Note: This project uses docs/ for Markdown source files, so this would conflict. Option A1 or the Actions approach below is safer.
Option B: GitHub Actions workflow
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@v4
This workflow triggers on every push to master, rebuilds the HTML, and deploys the html/ folder.
5. Custom Domains
GitHub Pages supports custom domains (e.g. learning.community-access.org instead of community-access.github.io/Learning-Room).
Setting up a custom domain
- 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) |
GitHub's current A record IPs for apex domains
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.
Domain verification
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.
6. HTTPS and Security
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.
7. Accessibility Considerations for Published Sites
Publishing a site does not automatically make it accessible. Consider the following for the published HTML output:
Navigation and landmarks
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
Skip navigation
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;
}
Focus management for single-page navigation
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.
Image alt text
All images in published pages should have appropriate alt attributes. Decorative images should use alt="" (empty, not missing) to be skipped by screen readers.
Color contrast
Run the published pages through an automated checker (axe, WAVE) after changes to the stylesheet to verify contrast ratios remain compliant.
Testing the published site
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.
Recommended post-deployment checks
- 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>
8. GitHub Actions and Continuous Deployment
Using the Actions workflow described in Section 4 means every push to master automatically rebuilds and redeploys the site.
Adding automated accessibility checks to the workflow
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 --exit
This 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
9. Limitations
| 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 |
10. Troubleshooting
Site not updating after a push
- 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)
404 on all pages except index
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).
HTTPS certificate not provisioning
- 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
Screen reader announces wrong page title
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