Search terms: hugo not updating · image not showing · white space instead of image ·
hard refresh · port 1313 already in use · stale public · serving pages from disk ·
--cleanDestinationDir · development vs production
Written 2026-08-09 after an image showed as blank white space. Cause turned out to be three separate things stacked, each of which alone would have hidden the problem.
Work down this list. Each step is cheap and rules out a whole class of cause.
# 1. Is more than one server running? (a second one silently takes another port)
pgrep -af "hugo server"
# 2. Is the file actually published? Build into a CLEAN dir and look.
rm -rf /tmp/hugo-check && hugo --destination /tmp/hugo-check
find /tmp/hugo-check -name "the-file-you-expect.*"
# 3. Does the server actually serve it? (proof, not a rendered page)
curl -s -o /dev/null -w "%{http_code} %{size_download} bytes\n" http://localhost:1313/path/to/file.png
# 4. Only then suspect the browser.
# Ctrl+Shift+R (Ctrl+Shift+F5 and Ctrl+F5 do the same thing)
Do step 3 before step 4. A 200 with a byte count is evidence. A page that looks right
can be lying via cache; a page that looks wrong can be a genuinely missing file.
port 1313 already in use, attempting to use an available port
Hugo does not fail when 1313 is taken. It quietly picks a random high port (38311, in the
real case) and starts anyway. If a forgotten server is still running in another terminal, the
browser tab pointed at :1313 keeps being served by the old process — so edits appear to
have no effect no matter how many times you restart.
That line scrolls past in the startup banner and is easy to miss. Check for it, or:
pgrep -af "hugo server" # expect exactly one
ss -tln | grep 131 # expect 127.0.0.1:1313
public/Hugo does not clean public/ between builds. Delete a source file and its built copy stays
forever. Over months, orphans accumulate — and a page whose source is broken keeps rendering
correctly from files that no longer have any source at all.
That is what hid a broken page for a month here: six orphaned images, one of them dated over five weeks earlier, with the page rendering fine the whole time.
Find the orphans:
find public -type f \( -name "*.png" -o -name "*.jpg" \) | while read f; do
find content -name "$(basename "$f")" -type f 2>/dev/null | grep -q . || echo "ORPHAN: $f"
done
Fix — public/ is disposable build output, gitignored, never deployed from here:
rm -rf public && hugo
hugo --cleanDestinationDir does roughly the same by asking Hugo to remove what it doesn’t
recognise. rm -rf is simpler to reason about: nothing can survive, because nothing is there.
Check before nuking that nothing hand-made lives in
public/— aCNAME,.nojekyll,_redirects, orrobots.txtyou wrote yourself would not be regenerated. On this site there is nothing: every file is Hugo output.
“Serving pages from disk” in the startup banner means the server is serving public/
directly rather than rendering to memory — so a stale public/ is served to the browser, not
just left lying around. That appears after you run hugo before hugo server.
Images are cached hard. Change one and the browser will keep showing the old bytes through ordinary reloads and through Hugo restarts, because the problem is not on the server side.
Ctrl+Shift+R · Ctrl+Shift+F5 · Ctrl+F5 — all the same thing, all bypass cache.
Restarting Hugo fixes stale server output. Hard refresh fixes stale browser cache. They are different problems, and restarting Hugo will never fix the second one.
A renamed file dodges the cache entirely, which is why renaming an image can suddenly “break” something that was actually broken all along — the rename just removed the cached copy that was covering it up.
The underlying bug in the real case. A page at content/github/How to/index.md is a
leaf bundle (a folder containing index.md), served at /github/how-to/.

That relative path resolves against the page’s own URL, i.e.
/github/how-to/images/gitstatus.png. But the images lived in content/github/images/, which
Hugo publishes to /github/images/. The two never met.
| Reference style | Resolves against | Use when |
|---|---|---|
images/foo.png |
The page’s URL | Image is inside the page bundle |
/github/images/foo.png |
Site root | Image is in a shared section folder |
Both are valid. The mismatch is the bug. To confirm which you need, build clean and look at where the file actually lands:
rm -rf /tmp/hugo-check && hugo --destination /tmp/hugo-check
find /tmp/hugo-check -name "*.png"
Writing about a shortcode is not the same as using one. Hugo parses shortcodes before
markdown, so backticks do not protect them — a literal {{< relref >}} written inside
inline code is still executed, and with no arguments it fails the whole build:
failed to render shortcode "relref": ... invalid value; expected string
Escape with the comment form — {{< ... >}} renders as literal text. Fenced code blocks
(```) do protect shortcodes; inline backticks do not.
hugo server runs as development. hugo builds as production.
| development | production | |
|---|---|---|
draft: true |
shown | hidden |
| Future-dated content | shown | hidden |
| Minification | off | on, if configured |
| LiveReload injected | yes | no |
Seeing Environment: "development" in the server banner is normal, not a warning.
The trap is row one: a page that looks fine in hugo server can be absent from a real build.
Check before deploying:
grep -rln "^draft: true" content/
public/ — hugo --destination /tmp/hugo-check and inspect
that. public/ carries history you didn’t intend.curl, not eyes — an HTTP code and a byte count can’t be faked by a cache.pgrep -af "hugo server" when anything seems not to update.The thread running through all four: something old lying around made a broken thing look correct. The fix in every case is to check the actual artifact rather than the convenient summary.