GitHub Pages: Complete Guide to Hosting Static Websites

GitHub Pages: Complete Guide to Hosting Static Websites
GitHub Pages is a free static site hosting service built into every GitHub repository. It serves HTML, CSS, JavaScript, and other static files directly from a branch or a build workflow — no server configuration, no hosting bills, no maintenance.
This guide covers everything: the difference between user pages and project pages, deploying plain HTML, deploying React/Next.js apps with GitHub Actions, configuring a custom domain with proper DNS records, enabling HTTPS, and fixing the most common issues.
Types of GitHub Pages Sites
User and Organisation Pages
Every GitHub account (personal or organisation) gets exactly one user/org page:
- Repository naming: Must be
<username>.github.io(e.g.,alice.github.io) - URL:
https://alice.github.io - Source: Always served from the
main(ormaster) branch - Best for: Personal portfolio, blog, resume, or professional landing page
# Create your user page repository
gh repo create alice.github.io --public
git clone https://github.com/alice/alice.github.io
cd alice.github.io
echo "<h1>Hello World</h1>" > index.html
git add index.html
git commit -m "initial page"
git push origin main
# Live at https://alice.github.io within ~60 secondsProject Pages
Every repository can have a project page, regardless of how many you have:
- URL:
https://alice.github.io/repository-name - Source: Configurable — a branch, a
/docsfolder, or a GitHub Actions build - Best for: Documentation for a library, landing pages for open-source projects, course materials
Quick Deploy: Plain HTML
The simplest possible deployment — static HTML files pushed to a branch:
- Go to your repository → Settings → Pages
- Under Source select Deploy from a branch
- Choose main branch and / (root) folder
- Click Save
Within 1-3 minutes, your site is live. GitHub shows you the URL at the top of the Pages settings page.
Folder structure for a simple site:
my-site/
├── index.html ↠Homepage (required)
├── about.html
├── styles/
│ └── main.css
└── images/
└── profile.jpgDeploying a React App with GitHub Actions
React apps need a build step (npm run build) that produces the static files to serve. GitHub Pages cannot run npm — you need GitHub Actions to build first, then deploy the output.
Method 1: Deploy to gh-pages Branch (Classic)
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch: # Allows manual triggering
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
# For Create React App: set the base path
PUBLIC_URL: /my-repo-name
- name: Deploy to gh-pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build # or ./dist for ViteAfter this workflow runs, switch Pages source to the gh-pages branch.
Method 2: GitHub Actions Pages (Modern)
GitHub now supports deploying directly from an Actions artifact without needing a separate branch:
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Setup Pages
uses: actions/configure-pages@v4
- run: npm ci
- run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist # Vite output; use ./build for CRA
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4In Settings → Pages → Source, select GitHub Actions (not a branch).
Deploying Astro, Vue, or Next.js Static Export
Astro
# .github/workflows/deploy.yml
- name: Build Astro site
run: npm run build # Outputs to ./dist by default
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./distastro.config.mjs:
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://alice.github.io',
base: '/my-repo', // omit if using a custom domain
});Next.js Static Export
Next.js can generate a static export for GitHub Pages:
// next.config.js
const nextConfig = {
output: 'export',
basePath: '/my-repo', // omit if using a custom domain
images: { unoptimized: true }, // GitHub Pages doesn't support Next.js image optimization
};
export default nextConfig;Build step:
npm run build # generates ./out directoryWorkflow:
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: ./outLimitation: Next.js static export does not support server-side rendering, server components that fetch data at request time, or API routes. If you need those features, use Vercel or a Node.js host instead of GitHub Pages.
Custom Domain Setup
Using yourname.com instead of alice.github.io is free and straightforward.
Step 1: Add the Custom Domain in GitHub
- Settings → Pages → Custom domain
- Enter your domain (e.g.,
alice.comorblog.alice.com) - Click Save
GitHub creates a CNAME file in your repository root containing your domain name. Do not delete this file.
Step 2: Configure DNS Records
For an apex domain (e.g., alice.com — no subdomain):
Add A records pointing to GitHub's IP addresses:
Type Name Value
A @ 185.199.108.153
A @ 185.199.109.153
A @ 185.199.110.153
A @ 185.199.111.153Also add an AAAA record (IPv6):
Type Name Value
AAAA @ 2606:50c0:8000::153
AAAA @ 2606:50c0:8001::153
AAAA @ 2606:50c0:8002::153
AAAA @ 2606:50c0:8003::153For a subdomain (e.g., docs.alice.com or www.alice.com):
Add a CNAME record:
Type Name Value
CNAME docs alice.github.io.DNS propagation typically takes 5-30 minutes. GitHub checks your DNS configuration and shows a green checkmark in Pages settings when it is correct.
Step 3: Verify DNS Propagation
# Check A records
dig alice.com +noall +answer
# Check CNAME records
dig docs.alice.com +noall +answer
# Should show GitHub's IPsStep 4: Enable HTTPS
Once DNS propagates:
- In Settings → Pages, check Enforce HTTPS
- GitHub automatically provisions a Let's Encrypt certificate
- Your site now serves HTTPS and redirects HTTP → HTTPS automatically
Certificate provisioning takes up to 24 hours after DNS changes propagate. If you see "Not yet available" — wait, it will appear.
The CNAME File and Deployment
If your deployment workflow overwrites the repository root (publishing the contents of /dist or /out), the CNAME file may get deleted on each deployment, breaking your custom domain.
Fix: Add your domain to the CNAME file inside your public/ or static assets directory so it gets copied to the build output:
# For Create React App
echo "alice.com" > public/CNAME
# For Vite
echo "alice.com" > public/CNAME
# For Astro
echo "alice.com" > public/CNAME
# For Next.js static export
echo "alice.com" > public/CNAMEJekyll: GitHub Pages' Native Framework
By default, GitHub Pages uses Jekyll to process files. Jekyll is a static site generator that converts Markdown to HTML. You can use it without any workflow:
---
layout: default
title: My Blog Post
date: 2026-04-18
---
# My Blog Post
This is the content in **Markdown**.Place Markdown files in your repository with Jekyll front matter (the --- block) and GitHub Pages builds them automatically. No build step needed.
Disable Jekyll (if you're using a different framework):
touch .nojekyll
git add .nojekyll
git commit -m "disable jekyll processing"Without .nojekyll, Jekyll ignores directories starting with _ (like _next from Next.js). If your site has blank pages after deploying, add .nojekyll.
Troubleshooting Common Issues
Site shows 404 after deployment
- Check that your workflow ran successfully in the Actions tab
- Verify your Pages source is set correctly (branch vs Actions)
- For project pages, ensure
index.htmlexists in the root of the published directory - Check for a
.nojekyllfile if using a framework that generates_directories
Custom domain shows "not secure" warning
- Let's Encrypt provisioning takes up to 24 hours
- The DNS records must be pointing to GitHub's IPs (verify with
dig) - Check "Enforce HTTPS" is enabled in Pages settings
React/Vue app shows blank page at project page URL
The app's router needs to know the base path. For React Router:
// main.jsx
<BrowserRouter basename="/my-repo">For Vue Router:
const router = createRouter({
history: createWebHistory('/my-repo/'),
routes,
});GitHub Actions workflow fails on deploy step
Ensure the workflow has the correct permissions:
permissions:
pages: write
id-token: writeWithout these permissions the deploy step will fail with a 403 error.
GitHub Pages Limits and Alternatives
| Limit | Value |
|---|---|
| Site size | 1 GB |
| Monthly bandwidth | 100 GB |
| Build timeout | 10 minutes |
| Builds per hour | 10 |
| Supports backend/server code | No |
| Supports databases | No |
When GitHub Pages is not enough:
- Your app needs server-side code (APIs, SSR) → Use Vercel or Netlify
- You need a database → Use Railway, Render, or Fly.io
- You need persistent storage or background jobs → Use a cloud provider (AWS, GCP, Azure)
- Your monthly bandwidth exceeds 100 GB → Use a CDN (Cloudflare, Fastly) in front of your Pages site
Frequently Asked Questions
Q: Is GitHub Pages really free forever?
Yes, for static sites within the published limits. There are no hidden costs. GitHub has provided this service since 2008. The limitations (no server-side code, no databases, 100 GB bandwidth) keep it sustainable as a free tier.
Q: Can I use GitHub Pages for a commercial website?
Yes, within the Terms of Service. You cannot use it to run an e-commerce transaction processor or a high-traffic advertising network, but a business landing page, product documentation, or marketing site is explicitly allowed.
Q: How do I redirect a page on GitHub Pages?
With Jekyll's redirect-from plugin (built into GitHub Pages):
---
redirect_from: /old-url
---Or add a meta refresh in an HTML file:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0; url=/new-url">
</head>
</html>Q: Can I host multiple websites from one repository?
Each repository gets one GitHub Pages site. For multiple sites, use multiple repositories. Alternatively, use a single site with subdirectories (alice.github.io/project-a/, alice.github.io/project-b/).
Key Takeaway
GitHub Pages is the fastest and cheapest way to deploy a static website. For plain HTML or Jekyll, it requires zero configuration — push to main and your site is live. For React, Vue, Astro, or Next.js, a simple GitHub Actions workflow builds your app and deploys the output. Custom domains with HTTPS are free and straightforward to configure. The main limitation is no server-side code — for anything requiring APIs or a database, move to Vercel or a full cloud provider.
Read next: Introduction to GitHub Actions: Automation Redefined →
Part of the GitHub Mastery Course — engineering the host.
