Building Static Site with Hugo Clarity Theme

Static site generators like Hugo allow you to build fast, secure, and highly customizable websites without the overhead of server-side logic. Whether you're creating a personal blog, research notebook, or engineering portfolio, this guide provides a complete, automation-focused setup for a professional web presence.

Environment Setup (Installation Scripts)

The development environment requires several tools: Go (the required runtime for building Hugo from source and for certain features), Hugo(the core static site generator, including Sass/SCSS and other advanced features), and essential development tools (git, gh, pre-commit utilities).

Installing go
We begin with a Bash script to install Go programming language. The script performs architecture detection, checks for existing versions, downloads the Go archive, installs it to a local directory, and ensures your shell environment is updated to include Go in the system path.

 1
 2# Golang Installation
 3install_go() {
 4  # Determine OS and architecture
 5  local os arch
 6  case "$(uname -s)" in
 7    Linux*) os="linux" ;;
 8    Darwin*) os="darwin" ;;
 9    *) echo "Unsupported OS"; return 1 ;;
10  esac
11  case "$(uname -m)" in
12    x86_64*) arch="amd64" ;;
13    arm64*) arch="arm64" ;;
14    *) echo "Unsupported architecture"; return 1 ;;
15  esac
16
17  # Get the latest Go version
18  local version
19  version=$(curl -fsS https://go.dev/VERSION?m=text | head -n1) || {
20    echo "Failed to get Go version"
21    return 1
22  }
23
24  # Validate version string
25  if [[ -z "$version" || ! "$version" =~ ^go[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
26    echo "Invalid Go version string: '$version'"
27    return 1
28  fi
29
30  # Check if Go is already installed with the same version
31  if command -v go &> /dev/null && [[ "$(go version)" =~ $version ]]; then
32    echo "Go $version is already installed"
33    return 0
34  fi
35
36  # Set archive path and download URL
37  local filename="${version}.${os}-${arch}.tar.gz"
38  local archive_path="$HOME/Downloads/$filename"
39  local url="https://go.dev/dl/$filename"
40
41  # Download archive if not already cached
42  if [[ -f "$archive_path" ]]; then
43    echo "Using cached archive: $archive_path"
44  else
45    echo "Downloading from: $url"
46    wget --secure-protocol TLSv1_3 -O "$archive_path" "$url" || {
47      echo "Download failed"
48      return 1
49    }
50  fi
51
52  # Remove existing Go and extract new version
53  rm -rf "$HOME/.local/go"
54  tar -C "$HOME/.local" -xzf "$archive_path" || {
55    echo "Extraction failed"
56    return 1
57  }
58
59  # Update PATH in shell config
60  local shell_config
61  shell_config=$(find "$HOME" -maxdepth 1 \( -name ".bash_aliases" -o -name ".zshrc" \) -print -quit)
62  if [[ -z "$shell_config" ]]; then
63    echo "No shell configuration file found"
64    return 1
65  fi
66
67  if ! grep -q 'export PATH=.*go/bin' "$shell_config"; then
68    echo 'export PATH="$HOME/.local/go/bin:$PATH"' >> "$shell_config"
69    echo "Updated PATH in $shell_config"
70  fi
71
72  export PATH="$HOME/.local/go/bin:$PATH"
73  echo "Go $version installed in $HOME/.local/go"
74  echo "Run 'source $shell_config' or restart your shell to use Go"
75  go version || echo "Go not found in PATH"
76}
77install_go

Installing hugo
Then we'll install Hugo (v0.150.0) using the official .deb files from the Hugo GitHub repository. The Bash function defines the Hugo version, architecture, and proceeds to downloading the standard and extended Hugo .deb files if they don't already exist locally and installs them using the Debian package manager.

 1# Hugo Installation
 2install_hugo() {
 3  local Version="0.150.0"
 4  local Arch="amd64"
 5  local OS="linux"
 6  local base_url="https://github.com/gohugoio/hugo/releases/download/v${Version}"
 7
 8  local standard_deb="hugo_${Version}_${OS}-${Arch}.deb"
 9  local extended_deb="hugo_extended_${Version}_${OS}-${Arch}.deb"
10
11  if [ ! -f "${standard_deb}" ] || [ ! -f "${extended_deb}" ]; then
12    echo "Downloading Hugo v${Version}..."
13    wget --secure-protocol TLSv1_3 "${base_url}/${standard_deb}"
14    wget --secure-protocol TLSv1_3 "${base_url}/${extended_deb}"
15  else
16    echo "Hugo v${Version} DEBs already downloaded."
17  fi
18
19  echo "Installing Hugo..."
20  sudo apt install -y -f ./"${standard_deb}" ./"${extended_deb}"
21}
22install_hugo

Installing imagemagick, git, gh, and pre-commit
Finally, we'll use the Debian package manager (apt) to install essential development tools. ImageMagick(magick) provides powerful utilities for image conversion and favicon/logo generation, which we’ll use later when customizing the Hugo site. Git(git) manages version control and theme integration, while the GitHub CLI (gh) streamlines repository setup and deployment. We’ll also install Python utilities (python3-pip, python3-venv, pipx) for virtual environment management and installing tools like pre-commit, which enforces code quality and security checks before commits.

 1# enter sudo user password when prompted
 2install_tools_via_package_manager() {
 3  local pkg_manager
 4  local install_cmd
 5  local tools="imagemagick git gh python3-pip python3-venv pipx"
 6
 7  if [ -f /etc/os-release ]; then
 8    source /etc/os-release
 9    case $ID in
10      debian | ubuntu)
11        pkg_manager="apt"
12        install_cmd="sudo apt -y install"
13        sudo apt update
14        ;;
15      centos | rhel | fedora)
16        pkg_manager="dnf"
17        install_cmd="sudo dnf -y install"
18        ;;
19      opensuse)
20        pkg_manager="zypper"
21        install_cmd="sudo zypper -y install"
22        ;;
23      *)
24        echo "Unsupported Linux distribution ($ID)."
25        return 1
26        ;;
27    esac
28  else
29    echo "Could not detect OS."
30    return 1
31  fi
32
33  echo "Installing $tools using $pkg_manager..."
34  if ! $install_cmd $tools; then
35    echo "Failed to install tools."
36    return 1
37  fi
38  
39  # Ensure pipx environment is ready
40  if ! command -v pipx &> /dev/null; then
41    echo "pipx not found. Aborting pre-commit installation."
42    return 1
43  fi
44
45  echo "Installing pre-commit using pipx..."
46  if ! pipx install pre-commit; then
47    echo "Failed to install pre-commit."
48    return 1
49  fi
50}
51install_tools_via_package_manager

Project Initialization & Configuration

This section, uses the installed gh, git and hugo tools to set up the repository, site structure, and initial configuration.

Define Core Variables
Start by defining the necessary environment and project variables. This centralizes all configurations for the repository, Hugo project, and custom domain. These variables will be used throughout the process.

 1# Git variables
 2GIT_USER_NAME="th3b1rdm2n"
 3GIT_USER_HOMEPAGE="https://github.com/$GIT_USER_NAME"
 4GIT_REPO_NAME="$GIT_USER_NAME.github.io"
 5GIT_REPO_URL="https://github.com/$GIT_USER_NAME/$GIT_REPO_NAME.git"
 6GIT_REPO_DESCRIPTION="A Digital Nest for Learning, Living and Leading with Intent."
 7GIT_MAIN_BRANCH="production"
 8GIT_DEV_BRANCH="development"
 9GIT_REMOTE_NAME="origin"
10
11# Hugo variables
12HUGO_SITENAME="$GIT_REPO_NAME"
13HUGO_PROJECT_ROOT="$HOME/$HUGO_SITENAME"
14HUGO_THEME_REPO_URL="https://github.com/chipzoller/hugo-clarity"
15HUGO_THEME_DIR="themes/$(basename "$HUGO_THEME_REPO_URL")"
16
17# Custom variables
18CUSTOM_DOMAIN="th3b1rdm2n.site"

Initialize the Project
Next, we'll authenticate to GitHub using the gh command-line tool and create a new public repository.

1# Authenticate to Github and create remote repository
2gh auth status # Check authentication status
3gh auth login -h github.com -p https -w # Authenticate to GitHub
4gh auth setup-git # Configure Git to use GitHub CLI as credential helper
5gh repo create "$GIT_REPO_NAME" --public --homepage "$GIT_USER_HOMEPAGE" --description "$GIT_REPO_DESCRIPTION" # Create remote repo

We'll then initialize the local Git repository, create a new Hugo site, add the Clarity theme as a submodule, and push to remote repository.

 1
 2# Set project root and clone the theme repository
 3cd $(dirname "$HUGO_PROJECT_ROOT")
 4hugo new site "$HUGO_SITENAME" --format yaml
 5cd "$HUGO_SITENAME"
 6hugo mod init "$HUGO_SITENAME"
 7
 8# Initialize Git 
 9git init . # Initialize local Git repo
10git config --local user.name "$GIT_USER_NAME" # Set local Git username
11git config --local user.email "" # Set local Git email
12git branch -M "$GIT_MAIN_BRANCH" # Rename default branch to production
13git submodule add $HUGO_THEME_REPO_URL "$HUGO_THEME_DIR"  # add a hugo theme as submodule
14git commit --allow-empty -m "chore: initial commit - $(date +%F_%H:%M)" # initial empty commit
15git remote add "$GIT_REMOTE_NAME" "$GIT_REPO_URL" # Add remote origin
16git push -u "$GIT_REMOTE_NAME" "$GIT_MAIN_BRANCH" # Push production branch to remote

Customize the Project
We'll then configure pre-commit hooks to ensure code quality, add a .gitignore file to exclude unnecessary files, add a CNAME file for gh-pages to use custom domain, copy the theme's exampleSite and icons folder into the project root and static folders respectively, update the images of the icons folder, and modification to various files as shown in the walkthrough video.

  1
  2# Create pre-commit config file
  3cat > "$HUGO_PROJECT_ROOT/.pre-commit-config.yaml" <<EOF
  4repos:
  5- repo: https://github.com/pre-commit/pre-commit-hooks
  6  rev: v5.0.0
  7  hooks:
  8  - id: check-added-large-files
  9    args: ["--maxkb=102400", "--enforce-all"]
 10- repo: https://github.com/zricethezav/gitleaks
 11  rev: v8.18.0
 12  hooks:
 13  - id: gitleaks
 14EOF
 15pre-commit install # Install pre-commit hooks
 16pre-commit autoupdate # Update hook repos to latest version
 17pre-commit run --all-files # Run pre-commit hooks on all files
 18
 19# add .gitignore file
 20cat > "$HUGO_PROJECT_ROOT/.gitignore" <<EOF
 21# IDE
 22**/.code-workspace
 23**/.idea
 24**/.vscode/
 25!**/.vscode/extensions.json
 26!**/.vscode/settings.json
 27
 28# Go
 29**/*.out
 30**/*.test
 31**/vendor/
 32
 33# Hugo
 34**/resources
 35**/public
 36
 37# JavaScript
 38**/.angular/
 39**/.next/
 40**/.node_repl_history
 41**/.npm
 42**/.nuxt/
 43**/.parcel-cache/
 44**/.temp
 45**/.vue-cli-service/
 46**/*.js.map
 47**/*.min.js
 48**/*.ts.map
 49**/*.tsbuildinfo
 50**/coverage/
 51**/dist/
 52**/e2e/
 53**/node_modules/
 54**/npm-debug.log
 55**/package-lock.json
 56**/public/
 57**/yarn-debug.log
 58**/yarn-error.log
 59
 60# Miscellaneous
 61**/*.bak
 62**/*.log
 63**/*.swp
 64**/.env
 65EOF
 66
 67# Add CNAME file at the project root for custom domain mapping
 68echo -e "$CUSTOM_DOMAIN\nwww.$CUSTOM_DOMAIN" > "$HUGO_PROJECT_ROOT/CNAME"
 69
 70# Use the exampleSite in developing and modifying yours
 71cp -a "$HUGO_THEME_DIR"/exampleSite/* $HUGO_PROJECT_ROOT/ # Copy theme's exampleSite content into the root of your Hugo project
 72cp -a "$HUGO_THEME_DIR"/static/icons $HUGO_PROJECT_ROOT/static/ # Copy theme's static icons to your project's static directory
 73
 74# Create a logo - to see list run: `convert -list font | less`
 75generate_ssg_image() {
 76  local input_image="" output_dir="" logo_text=""
 77
 78  # Parse CLI arguments
 79  while [[ "$#" -gt 0 ]]; do
 80    case "$1" in
 81      -i|--input) input_image="$2"; shift 2 ;;
 82      -o|--output) output_dir="$2"; shift 2 ;;
 83      -l|--logo) logo_text="$2"; shift 2 ;;
 84      -*|*) echo "Usage: $FUNCNAME -i <input_image> -o <output_dir>"; return 1 ;;
 85    esac
 86  done
 87
 88  # Validate input
 89  [[ -z "$input_image" || -z "$output_dir" ]] && {
 90    echo "Usage: $FUNCNAME -i <input_image> -o <output_dir>"; return 1; }
 91  [[ ! -f "$input_image" ]] && { echo "File not found: $input_image"; return 1; }
 92
 93  mkdir -p "$output_dir"
 94
 95  local transparent_image="$output_dir/th3b1rdm2n.png"
 96  convert "$input_image" -fuzz 10% -transparent black "$transparent_image"
 97
 98  declare -A output_names=(
 99    [16]="favicon-16x16.png"
100    [32]="favicon-32x32.png"
101    [150]="mstile-150x150.png"
102    [180]="apple-touch-icon.png"
103    [192]="android-chrome-192x192.png"
104    [256]="android-chrome-256x256.png"
105    [512]="android-chrome-512x512.png"
106  )
107
108  echo "Generating icons from $transparent_image..."
109  for size in "${!output_names[@]}"; do
110    convert "$transparent_image" -resize "${size}x${size}" "$output_dir/${output_names[$size]}"
111    echo "Generated: ${output_names[$size]}"
112  done
113
114  convert "$output_dir/favicon-16x16.png" "$output_dir/favicon.ico"
115  
116  # Create a logo - to see list run: `convert -list font | less`
117  convert -size 140x38 xc:none -gravity center -stroke white -strokewidth 2 -fill black -pointsize 24 -font Liberation-Sans -annotate +0+0 "$logo_text" "$(dirname "$output_dir")/logos/logo.png"
118  echo "Done: All icons saved to $output_dir"
119}
120generate_ssg_image -i "$HOME/Downloads/th3b1rdm2n.png" -o "$HUGO_PROJECT_ROOT/static/icons" -l "bírd màn"

Git Workflow and Deployment

Branching and Initial Deployment
A structured Git workflow ensures that development work is isolated from the live production code. This step creates a development branch for ongoing changes and then merges to the production branch, triggering the configured GitHub Actions workflow to build and deploy your site. After the initial push, navigate to your repository settings to enable GitHub Actions as the deployment source for GitHub Pages.

 1git checkout -b "$GIT_DEV_BRANCH" # Create and switch to main branch
 2git add .  # add the modified and populated files
 3git commit -m "chore: modified and populated site files - $(date +%F_%H:%M)"
 4git push -u "$GIT_REMOTE_NAME" "$GIT_DEV_BRANCH"
 5
 6# Navigate to $GIT_USER_HOMEPAGE/$GIT_REPO_NAME/settings/pages. Switch the Source to `GitHub Actions`. Then run:
 7git switch "$GIT_MAIN_BRANCH"
 8git merge "$GIT_DEV_BRANCH"
 9git push -u "$GIT_REMOTE_NAME" "$GIT_MAIN_BRANCH" # Push main branch to remote
10git switch "$GIT_DEV_BRANCH" # Switch back to development branch

Configuring DNS Records
To make your site accessible via your custom domain, you must update your DNS settings. At your domain registrar (e.g., Namecheap), buy a domain of your choice, navigate to Advanced DNS and add the following records to point your domain to the correct GitHub Pages IP addresses and repository URL.

1open "https://ap.www.namecheap.com/Domains/DomainControlPanel/$CUSTOM_DOMAIN/advancedns"

Click on Add New Record and add A and CNAME records:

TypeHostValueTTL
A Record@185.199.108.153Automatic
A Record@185.199.109.153Automatic
A Record@185.199.110.153Automatic
A Record@185.199.111.153Automatic
CNAME Record@<your $GIT_REPO_NAME value>Automatic
1open $GIT_USER_HOMEPAGE/$GIT_REPO_NAME/settings/pages 

With DNS records set, finalize the connection in GitHub. Enter <your $CUSTOM_DOMAIN value> in the Settings > Pages > Custom domain input field. GitHub will provision the necessary SSL certificate. Your Hugo site is now live and accessible via your custom domain.