Building a Personal Website with Hugo and GitHub Pages
Table of Contents
#
Introduction

After wanting to create a personal website for a while, I finally took the plunge and built one using Hugo, the Congo theme, and GitHub Pages. The best part? It’s completely free to host, lightning-fast, and automatically deploys whenever I push changes to my repository.
In this article, I’ll walk you through exactly how I built this site, configured the deployment pipeline, and solved the challenges I encountered along the way.
Why Hugo and GitHub Pages? #
Before diving into the technical details, let me explain why I chose this stack:
Hugo is a static site generator written in Go that’s:
GitHub Pages is perfect because:
Project Structure #
Here’s what the final project structure looks like:
[rwgb.github.io/](http://rwgb.github.io/)
├── .github/
│ └── workflows/
│ └── hugo.yml # GitHub Actions workflow
├── content/
│ ├── posts/ # Blog posts
│ ├── projects/ # Project showcase
│ ├── [about.md](http://about.md/) # About page
│ └── [contact.md](http://contact.md/) # Contact page
├── static/ # Static assets
│ └── img/
├── themes/
│ └── congo/ # Theme (as git submodule)
├── .gitignore
├── .gitmodules
├── hugo.toml # Hugo configuration
└── [README.md](http://readme.md/)
Step 1: Setting Up Hugo #
First, I installed Hugo on my Mac using Homebrew:
brew install hugo
Then created a new Hugo site:
hugo new site [personal.site](http://personal.site/)
cd [personal.site](http://personal.site/)
Step 2: Adding the Congo Theme #
I chose the Congo theme for its modern design and extensive customization options. I added it as a git submodule:
git submodule add -b stable [https://github.com/jpanther/congo.git](https://github.com/jpanther/congo.git) themes/congo
Using a git submodule is important because it allows the theme to be updated independently while keeping your site’s repository clean.
Step 3: Configuring Hugo #
The hugo.toml file is where all the magic happens. Here’s a breakdown of the key configuration:
baseURL = '[https://rwgb.github.io/](https://rwgb.github.io/)'
languageCode = 'en-us'
title = 'Ralph Brynard | Developer & Creator'
theme = 'congo'
enableRobotsTXT = true
summaryLength = 30
[pagination]
pagerSize = 10
[outputs]
home = ["HTML", "RSS", "JSON"]
Important Configuration Details #
- Pagination Update: I initially used the deprecated
paginateparameter but had to update it to the new format:
[pagination]
pagerSize = 10
- Author Configuration: The Congo theme requires author information in a specific structure:
[[languages.en.params.author](http://languages.en.params.author/)]
name = "Ralph Brynard"
image = "img/profile.jpg"
headline = "Developer & Content Creator"
bio = "Building innovative projects..."
links = [
{ github = "[https://github.com/rwgb](https://github.com/rwgb)" },
{ linkedin = "[https://linkedin.com/in/ralphbrynard](https://linkedin.com/in/ralphbrynard)" }
]
Step 4: Creating Content #
Creating content in Hugo is straightforward. Each blog post is a Markdown file with frontmatter:
---
title: "My Post Title"
date: 2025-11-07
description: "A brief description"
tags: ["tutorial", "hugo"]
categories: ["Web Development"]
draft: false
---
Your content here...
I organized content into logical directories:
content/posts/- Blog articlescontent/projects/- Project showcasescontent/about.md- About pagecontent/contact.md- Contact information
Step 5: The GitHub Actions Workflow #
This is where automation comes in. The workflow automatically builds and deploys the site whenever I push to the main branch.
Here’s the complete workflow file (.github/workflows/hugo.yml):
name: Deploy Hugo site to Pages
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
defaults:
run:
shell: bash
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build with Hugo
env:
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: hugo --minify --baseURL "$ steps.pages.outputs.base_url /"
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
environment:
name: github-pages
url: $ [steps.deployment.outputs.page](http://steps.deployment.outputs.page/)_url
runs-on: ubuntu-latest
needs: build
permissions:
pages: write
id-token: write
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Breaking Down the Workflow #
Triggers: The workflow runs on:
Permissions: Critical for GitHub Pages deployment:
permissions:
contents: read
pages: write
id-token: write
Build Job Steps:
Deploy Job:
Step 6: GitHub Repository Configuration #
Setting up the repository correctly is crucial:
Repository Name: Must be
username.github.io(in my case:rwgb.github.io)GitHub Pages Settings:
Git Submodules: Don’t forget to initialize and update submodules:
git submodule init
git submodule update
Challenges I Encountered #
Issue 1: Deprecated Actions #
Problem: The workflow initially failed with
actions/upload-pages-artifact@v2Solution: Updated to v3:
uses: actions/upload-pages-artifact@v3
Issue 2: Author Configuration #
Problem: Got errors like “can’t evaluate field name in type string”
Solution: The theme needed author as an object, not a string:
# Wrong
author = "Ralph Brynard"
# Correct
[[languages.en.params.author](http://languages.en.params.author/)]
name = "Ralph Brynard"
Issue 3: Deployment Failures #
Problem: “Failed to create deployment (status: 404)”
Solution: Had to:
Issue 4: Public Directory #
Problem: The
.gitignorewas ignoring thepublic/directorySolution: This is actually correct! GitHub Actions builds the site fresh on each deployment, so you don’t want to commit the generated files.
Local Development #
Testing locally is easy:
# Start development server
hugo server -D
# Visit [http://localhost:1313](http://localhost:1313/)
The -D flag includes draft posts. Hugo’s live reload makes development a breeze.
The .gitignore File #
Here’s my .gitignore setup:
# Hugo
/public/
/resources/_gen/
/.hugo_build.lock
# OS
.DS_Store
Thumbs.db
# Editor
.vscode/
.idea/
# Backup Files
*.bak
*.old
The public/ directory is ignored because GitHub Actions generates it during deployment.
Deployment Process #
The actual deployment flow:
- Push changes to the
mainbranch:
git add .
git commit -m "Add new blog post"
git push origin main
GitHub Actions triggers automatically
Build process:
Deploy process:
Performance and SEO #
The resulting site is blazingly fast:
Static HTML files (no server-side processing)
Minified assets
Served via GitHub’s CDN
Automatic HTTPS
SEO-friendly URLs
Cost Analysis #
Total cost: $0
Hugo: Free and open source
Congo theme: Free
GitHub Pages: Free for public repositories
GitHub Actions: 2,000 minutes/month free (way more than needed)
SSL Certificate: Included with GitHub Pages
Tips and Best Practices #
Use Extended Hugo: Many themes require SCSS/SASS support
Submodules for themes: Keep themes updatable
Test locally first: Always run
hugo serverbefore pushing