DevOpsGitHub

GitHub Pages: Complete Guide to Hosting Static Websites

TT
TopicTrick Team
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 (or master) branch
  • Best for: Personal portfolio, blog, resume, or professional landing page
bash
# 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 seconds

Project 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 /docs folder, 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:

  1. Go to your repository → Settings → Pages
  2. Under Source select Deploy from a branch
  3. Choose main branch and / (root) folder
  4. 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:

text
my-site/
├── index.html          ← Homepage (required)
├── about.html
├── styles/
│   └── main.css
└── images/
    └── profile.jpg

Deploying 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)

yaml
# .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 Vite

After 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:

yaml
# .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@v4

In Settings → Pages → Source, select GitHub Actions (not a branch).


Deploying Astro, Vue, or Next.js Static Export

Astro

yaml
# .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: ./dist

astro.config.mjs:

javascript
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:

javascript
// 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:

bash
npm run build   # generates ./out directory

Workflow:

yaml
- run: npm run build
- uses: actions/upload-pages-artifact@v3
  with:
    path: ./out

Limitation: 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

  1. Settings → Pages → Custom domain
  2. Enter your domain (e.g., alice.com or blog.alice.com)
  3. 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:

text
Type  Name  Value
A     @     185.199.108.153
A     @     185.199.109.153
A     @     185.199.110.153
A     @     185.199.111.153

Also add an AAAA record (IPv6):

text
Type   Name  Value
AAAA   @     2606:50c0:8000::153
AAAA   @     2606:50c0:8001::153
AAAA   @     2606:50c0:8002::153
AAAA   @     2606:50c0:8003::153

For a subdomain (e.g., docs.alice.com or www.alice.com):

Add a CNAME record:

text
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

bash
# Check A records
dig alice.com +noall +answer

# Check CNAME records
dig docs.alice.com +noall +answer

# Should show GitHub's IPs

Step 4: Enable HTTPS

Once DNS propagates:

  1. In Settings → Pages, check Enforce HTTPS
  2. GitHub automatically provisions a Let's Encrypt certificate
  3. 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:

bash
# 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/CNAME

Jekyll: 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:

markdown
---
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):

bash
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.html exists in the root of the published directory
  • Check for a .nojekyll file 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:

javascript
// main.jsx
<BrowserRouter basename="/my-repo">

For Vue Router:

javascript
const router = createRouter({
  history: createWebHistory('/my-repo/'),
  routes,
});

GitHub Actions workflow fails on deploy step

Ensure the workflow has the correct permissions:

yaml
permissions:
  pages: write
  id-token: write

Without these permissions the deploy step will fail with a 403 error.


GitHub Pages Limits and Alternatives

LimitValue
Site size1 GB
Monthly bandwidth100 GB
Build timeout10 minutes
Builds per hour10
Supports backend/server codeNo
Supports databasesNo

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):

yaml
---
redirect_from: /old-url
---

Or add a meta refresh in an HTML file:

html
<!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.