Skip to content

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.

While the Hugo build process in GitHub Actions may report a “Success,” the browser console shows the following when visiting the live URL:

Terminal window
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.

  1. Case Sensitivity (The #1 Culprit): Does the filename in the static/ folder match the reference in your Markdown exactly? (e.g., Image.jpg vs image.jpg).
  2. BaseURL Configuration: Is your baseURL in config.toml/hugo.yaml pointing to the root domain or the sub-path of the GitHub repository?
  3. Submodule Fetching: If your theme or images are in a git submodule, is your GitHub Action configured to fetch them?
  4. Relative vs Absolute Paths: Are you using /images/pic.png (absolute to root) or images/pic.png (relative to page)?
  5. Extended Version: Are you using Hugo “Extended” features (like WebP processing) but using the standard Hugo binary in your Action?

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:

hugo.yaml
baseURL: "https://username.github.io/my-repo-name/"
canonifyURLs: true
relativeURLs: false

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:

Terminal window
# Find all images and compare with your markdown references
find ./static/images -type f
grep -r "images/" ./content

Architect’s Note: Standardize all filenames to lowercase to prevent environment-specific drift.

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/"

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:

Terminal window
# 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.