Publishing with GitHub Pages
Free static site hosting, custom domains, HTTPS, and accessibility.
Listen
Transcript
Alex: Welcome back. Today we're publishing a website without renting a server or setting up a separate hosting account. GitHub Pages lets a repository serve a static website, right from GitHub.
Jamie: Static website is one of those phrases that sounds simple until it doesn't. What does static mean here?
Alex: It means GitHub Pages serves files as they already exist: HTML, CSS, JavaScript, images, PDFs, and files like that. It does not run server-side code, so no PHP request handlers, no Python web app, no Node.js server, and no private database behind the page.
Jamie: So it is great for things that can be delivered as files: documentation, workshop materials, a project landing page, a portfolio, maybe a blog.
Alex: Exactly. The default address for a project site usually looks like https://username.github.io/repo-name/. For a user or organization Pages site, where the repository is named username.github.io, the address becomes https://username.github.io/.
Jamie: Let's say I have a repository with a small site in it. Where do I turn Pages on?
Alex: Open the repository on GitHub.com, then go to Settings. In the left sidebar, find the Code and automation group, and choose Pages. That page is where you choose how GitHub should build and publish the site.
Jamie: And the accessibility detail here is helpful. Settings is a link in the repository navigation, and Pages is also a link in the settings sidebar under that Code and automation grouping.
Alex: Right. Under Build and deployment, you choose a source. The two big choices are deploying directly from a branch, or using GitHub Actions to build and deploy.
Jamie: If I choose deploy from a branch, what am I picking?
Alex: You select the branch, commonly main or master, then select the folder. GitHub Pages supports either the repository root, written as slash, or the docs folder, written as docs slash. After you save, the first deployment usually takes a minute or two, and the published URL appears near the top of the Pages settings page.
Jamie: For low vision learners, the status indicators matter too. A successful deployment shows a green checkmark, and a failed one shows a red X. And the published site does not inherit GitHub's styling, so your own CSS has to handle contrast, spacing, and readable font sizes.
Alex: Choosing the publishing source is really a design decision. If you serve from the root, GitHub publishes files from the top level of the repository. If you serve from docs slash, GitHub publishes only that folder.
Jamie: I can see why docs slash is cleaner when the repository also has source files, scripts, tests, or notes that are not meant to be the website.
Alex: Yes. The docs folder keeps the published site separate from the working parts of the project. But when you need a build step, branch publishing may not be enough.
Jamie: This is where Jekyll comes in, right?
Alex: Yes. Jekyll is the classic static site generator associated with GitHub Pages. A static site generator takes source files, often Markdown plus templates, and produces plain HTML, CSS, and other assets. You can also use Hugo, Eleventy, or a Next.js static export, but those usually need GitHub Actions so the site can be built before it is published.
Jamie: And Actions can do more than build. It can run checks before the site goes live, including accessibility checks.
Alex: In this project, there is a specific wrinkle. The Markdown content has a pre-built HTML mirror in an html folder, generated by scripts/build-html.js.
Jamie: But GitHub Pages does not let me choose any folder I want when I deploy from a branch. It gives me root or docs slash.
Alex: Correct. One manual approach is to copy the contents of html into docs, then commit and push the docs folder. After that, set the Pages source to the master branch and the docs folder.
Jamie: What about renaming html to docs? That sounds tempting.
Alex: It can work in a different project, but only if docs is not already being used for something else. You would update the build script output path first, then move html to docs and commit the change. In this project, docs is used for Markdown source files, so copying the built output or using Actions is safer.
Jamie: So the automated version is the cleaner long-term path.
Alex: Yes. A Pages workflow can run on every push to master. It checks out the repository, sets up Node.js, installs dependencies, runs node scripts/build-html.js, uploads the html folder as the Pages artifact, and then deploys it.
Jamie: What if I don't want the github.io address? Can I use my own domain?
Alex: You can. In repository Settings, then Pages, enter the custom domain and save it. GitHub adds a CNAME file to the repository containing that domain name.
Jamie: Then the DNS provider has to point the domain at GitHub Pages.
Alex: For a subdomain, like learning.example.org, you create a CNAME record that points to the account's github.io host. For an apex domain, like example.org, you use four A records. The current GitHub Pages IP addresses are 185.199.108.153, 185.199.109.153, 185.199.110.153, and 185.199.111.153.
Jamie: DNS can be slow, so this is not always instant.
Alex: Right. DNS changes can take up to 48 hours to propagate, and GitHub Pages will keep checking. GitHub also recommends verifying the custom domain in your account or organization Pages settings, because verification helps prevent someone else from claiming your domain on Pages if you temporarily remove it.
Jamie: What about HTTPS? Do learners have to buy a certificate?
Alex: No. GitHub Pages enforces HTTPS automatically for github.io subdomains. For custom domains, once DNS has propagated, you can enable Enforce HTTPS, and GitHub Pages provisions a certificate through Let's Encrypt.
Jamie: So the visitor gets redirected from HTTP to HTTPS once enforcement is on.
Alex: Exactly. One security reminder, though: a Pages site is public static hosting. Do not put secrets, API keys, private data, or anything confidential into files that will be published. Client-side JavaScript is visible to visitors, too.
Jamie: Publishing is exciting, but accessibility cannot stop at the repository. What should people check in the actual site?
Alex: Start with semantic HTML. Use headings in a real outline, a main landmark for the page's primary content, navigation where navigation belongs, and footer or aside only when they make sense. Screen reader users should be able to move by headings, landmarks, links, and form controls and get a coherent picture of the page.
Jamie: Skip links are one of those tiny features that make a site feel much less exhausting.
Alex: Yes. Add a skip link near the top of the page that jumps to the main content, and make sure it becomes visible when focused. It helps keyboard users bypass repeated navigation.
Jamie: What changes for single-page sites, where the URL or content changes without a full reload?
Alex: Focus needs attention. After a route change, move focus to the main heading or main region, and update the document title so assistive technology announces the correct page. Otherwise, a listener may hear the old page title while the visible content has changed.
Jamie: And the basics still matter: images and color.
Alex: Definitely. Give meaningful images useful alt text, mark decorative images with empty alt text, and check color contrast for text, controls, focus indicators, and links. Also test zoom and text resizing, because published sites need to remain readable when someone increases magnification.
Jamie: After deployment, how would you test without making it a giant ritual?
Alex: Open the published URL, not just the local files. Use the keyboard to move through links and controls, check that focus is visible, inspect headings and landmarks with a screen reader, and confirm the page title makes sense. Automated tools like axe, Lighthouse, or pa11y can help, but they do not replace a real navigation pass.
Jamie: The troubleshooting note about wrong page titles feels easy to miss.
Alex: It is. If a screen reader announces the wrong page title, check the title element in the HTML. For a single-page app, also make sure route changes update the title before or at the same time the new content is presented.
Jamie: What about the 404 page? GitHub Pages will show something if a page is missing, but can we make that experience better?
Alex: Yes. Add a 404.html file to the root of the published output. Make it plain and accessible: say the page was not found, provide a link back home, and include links to search, documentation, or the main project page if those are useful.
Jamie: Manual publishing is fine once, but it can get stale. What does continuous deployment add?
Alex: It makes publishing repeatable. A GitHub Actions workflow can build the site on each push, run checks, upload the Pages artifact, and deploy it. The workflow needs permissions such as contents read, pages write, and id-token write so it can publish securely.
Jamie: You mentioned accessibility checks earlier. Where do those fit?
Alex: Put them after the build and before the deploy, or in a separate job that the deploy depends on. Depending on the project, that might be npm test, an HTML validator, pa11y, axe-based tests, or link checking. Some teams fail the deployment on serious accessibility errors, while others start with warnings and tighten the rules over time.
Jamie: What are the limits? Because Pages sounds simple, but it is not a full web host.
Alex: That's the key. GitHub Pages is for static output, so it will not run server-side code, host a database, process private form submissions by itself, or protect secret files. It is also not ideal for very large media-heavy sites or anything that needs a custom backend.
Jamie: If I push a change and the site does not update, where should I look first?
Alex: Check the Pages settings and the Actions or deployment history. Make sure the selected branch and folder are correct, the commit reached that branch, and the workflow actually finished. Also try a hard refresh, because browser caching can make an old page look like a failed deployment.
Jamie: What about the scary one: the home page works, but every other page is a 404?
Alex: That often comes from paths. Project Pages live under slash repo-name slash, so root-relative links can break if the site generator assumes it is hosted at the domain root. Single-page apps may also need a fallback strategy, and regular sites need the generated files to match the URLs you are linking to.
Jamie: And if the HTTPS certificate is stuck?
Alex: Confirm the DNS records are correct, remove conflicting records, wait for propagation, and then try Enforce HTTPS again when GitHub shows the domain is ready. If the certificate is still not provisioning, the DNS check is usually the place to start.
Jamie: So GitHub Pages is not magic, but it is a very practical publishing path when the output is static and the checks are visible.
Alex: Exactly. Pick the right source, build when you need to, protect the domain and HTTPS settings, and test the published site as a real website. That turns a repository into something people can actually read, navigate, and use.
Workshop Content
Companion Podcast and Transcript
Use audio and transcript companions to review concepts in a conversational format.
Publishing with GitHub Pages
Companion audio: this episode reinforces key ideas and may not be a word-for-word reading of this page.
Open audio file (external) Full transcript source (external)
Transcript preview
Alex: Welcome back. Today we're publishing a website without renting a server or setting up a separate hosting account. GitHub Pages lets a repository serve a static website, right from GitHub.
Jamie: Static website is one of those phrases that sounds simple until it doesn't. What does static mean here?
Alex: It means GitHub Pages serves files as they already exist: HTML, CSS, JavaScript, images, PDFs, and files like that. It does not run server-side code, so no PHP request handlers, no Python web app, no Node.js server, and no private database behind the page.
Jamie: So it is great for things that can be delivered as files: documentation, workshop materials, a project landing page, a portfolio, maybe a blog.
Appendix W: 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.
Reference companion to: Chapter 08: Open Source Culture | Also relevant: Chapter 22: What Comes Next
Authoritative source: GitHub Docs: About Pages
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.
Learning Cards: GitHub Pages Overview
Screen reader users
- The Pages settings are under Settings (gear icon in repository navigation) then "Pages" in the left sidebar under the "Code and automation" group heading
- Branch and folder selectors in the Pages settings are standard
selectelements -- navigate with arrow keys to choose your publishing source - After deployment, the published URL appears as a link at the top of the Pages settings page
Low vision users
- GitHub Pages settings use the same layout as other repository settings -- look for the "Pages" link in the left sidebar after clicking Settings
- The deployment status indicator shows a green checkmark for successful deployments and a red X for failures
- Published sites inherit no special styling from GitHub -- ensure your site's CSS provides adequate contrast and font sizing
Sighted users
- Find Pages settings via the repository's Settings tab, then "Pages" in the left sidebar under "Code and automation"
- The publishing source selector lets you choose a branch and folder (
/root or/docs) -- select your combination and click Save - After a successful deployment, a green banner with your site URL appears at the top of the Pages settings
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:
Learning Cards: Accessibility of Published Sites
Screen reader users
- Verify that published HTML pages include a skip-to-content link as the first focusable element -- press
Tabonce after the page loads to check - Confirm the page has a descriptive
titleelement and a singleH1heading -- these are announced first when the page opens - Test that all internal navigation links work and land on the correct heading or section
Low vision users
- Run an automated contrast checker (axe, WAVE) on published pages after any CSS changes to confirm text remains readable
- Test the published site at 200% browser zoom to verify layouts do not break or hide content
- If the site uses a custom theme, verify it works in both forced-colors mode (Windows High Contrast) and standard mode
Sighted users
- Check that the page has clear visual heading hierarchy -- headings should be visually distinct and match the HTML heading levels (H1, H2, H3)
- Verify all images have visible alt text or are clearly decorative -- missing alt attributes leave broken-image icons with no context
- Test keyboard navigation through the entire page by pressing Tab repeatedly to ensure focus is visible and flows logically
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.
Next: Appendix X: Resources
Back: Appendix V: GitHub Mobile
Teaching chapter: Chapter 08: Open Source Culture
Authoritative Sources
Use these official references when you need the current source of truth for facts in this chapter.
Section-Level Source Map
Use this map to verify facts for each major section in this file.
- How to Deploy a Static Website Directly from Your Repository: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart, About Git
- 1. What GitHub Pages Is: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart, About Git
- 2. Enabling GitHub Pages for a Repository: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart, About Git
- 3. Publishing Sources: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart
- 4. The html/ Folder in This Project: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart, GitHub Projects docs
- Option A2 - Rename html/ to docs/: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart
- 5. Custom Domains: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart
- 6. HTTPS and Security: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart, GitHub security features
- 7. Accessibility Considerations for Published Sites: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart, W3C Web Content Accessibility Guidelines (WCAG) 2 overview
- 8. GitHub Actions and Continuous Deployment: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart, About Git
- 9. Limitations: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart
- 10. Troubleshooting: GitHub Docs, home, GitHub Changelog, GitHub Pages docs, GitHub Pages quickstart