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