Fix: Hugo Images Missing on GitHub Pages vs Local Server
In a Hugo static site generator environment, a common discrepancy occurs where images render perfectly on a local development machine (Windows or macOS) but return 404 errors once deployed to GitHub Pages via GitHub Actions. As a Cloud Architect, I see this frequently when moving from case-insensitive local filesystems to the case-sensitive Linux environment used by GitHub Actions runners and GitHub Pages’ hosting infrastructure.
The Error
Section titled “The Error”While the Hugo build process in GitHub Actions may report a “Success,” the browser console shows the following when visiting the live URL:
GET https://username.github.io/repository/images/Hero-Banner.png 404 (Not Found)GET https://username.github.io/images/logo.svg 404 (Not Found)Locally, running hugo server shows all assets correctly.
Diagnostic Checklist
Section titled “Diagnostic Checklist”- Case Sensitivity (The #1 Culprit): Does the filename in the
static/folder match the reference in your Markdown exactly? (e.g.,Image.jpgvsimage.jpg). - BaseURL Configuration: Is your
baseURLinconfig.toml/hugo.yamlpointing to the root domain or the sub-path of the GitHub repository? - Submodule Fetching: If your theme or images are in a git submodule, is your GitHub Action configured to fetch them?
- Relative vs Absolute Paths: Are you using
/images/pic.png(absolute to root) orimages/pic.png(relative to page)? - Extended Version: Are you using Hugo “Extended” features (like WebP processing) but using the standard Hugo binary in your Action?
The Fix
Section titled “The Fix”1. Correcting the BaseURL and Pathing
Section titled “1. Correcting the BaseURL and Pathing”If your site is hosted at https://<user>.github.io/<repo>/, Hugo needs to know that assets are not at the root of the domain.
Update your hugo.yaml or config.toml:
baseURL: "https://username.github.io/my-repo-name/"canonifyURLs: truerelativeURLs: false2. Normalizing Case Sensitivity
Section titled “2. Normalizing Case Sensitivity”Linux-based runners (Ubuntu) used in GitHub Actions will treat Header.png and header.png as two different files. Local Windows/macOS environments often ignore this.
Run this command in your local terminal to identify mismatches:
# Find all images and compare with your markdown referencesfind ./static/images -type fgrep -r "images/" ./contentArchitect’s Note: Standardize all filenames to lowercase to prevent environment-specific drift.
3. Updating the GitHub Actions Workflow
Section titled “3. Updating the GitHub Actions Workflow”Ensure your workflow is explicitly pulling the correct Hugo version (Extended) and handling submodules if your assets are stored in a separate repository.
Update .github/workflows/hugo.yml:
jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: submodules: recursive # Crucial if images/themes are submodules fetch-depth: 0
- name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: 'latest' extended: true # Required if using Hugo Pipes for image processing
- name: Build run: hugo --minify --baseURL "https://username.github.io/my-repo-name/"4. Handling Image Processing in Markdown
Section titled “4. Handling Image Processing in Markdown”If you are using Page Bundles (images in the same folder as index.md), ensure your Hugo shortcodes or render hooks use .GetRemote or .Resources.GetMatch properly.
Avoid hardcoded paths in your templates:
# Avoid this:<img src="/static/images/photo.jpg">
# Use this (Hugo logic):{{ $image := .Resources.GetMatch "photo.jpg" }}<img src="{{ $image.RelPermalink }}">By aligning the baseURL in the CI/CD pipeline and enforcing lowercase naming conventions, you eliminate the pathing discrepancies between the local Go-based server and the production Linux environment.