Skip to content

docs: content-hash screenshot/asset URLs to fix stale caches#47

Merged
acoshift merged 1 commit into
mainfrom
docs-asset-fingerprint
Jun 23, 2026
Merged

docs: content-hash screenshot/asset URLs to fix stale caches#47
acoshift merged 1 commit into
mainfrom
docs-asset-fingerprint

Conversation

@acoshift

Copy link
Copy Markdown
Member

Problem

Screenshots lived in static/img/ and Hugo copies static/ to public/ verbatim — no content hash. When a screenshot is re-captured under the same filename (e.g. deploy-form.png), browsers and the CDN keep serving the stale cached copy. The site's CSS already avoided this (it pipes main.scss through resources.Fingerprint), but images did not.

Fix

Move static/img/assets/img/ and resolve images through Hugo Pipes resources.Fingerprint, so published URLs carry a content hash:

/img/deploy-form.png  →  /img/deploy-form.<sha256>.png

Because the hash is content-based, an unchanged screenshot keeps its URL (cache stays valid) and only a re-captured one gets a new URL → guaranteed cache miss. No manual version bumping, and it holds even under aggressive/immutable asset caching since the new URL is a genuinely different resource.

Changes

  • layouts/shortcodes/shot.html — resolves src from assets/ via resources.Get | resources.Fingerprint, nil-checks the light/dark variants, and hard-errors on a missing image. The author API (src="/img/x.png") is unchanged, so the 26 shot invocations across 22 screenshots in content/ needed no edits.
  • layouts/partials/navbar.html — logo fingerprinted the same way.
  • scripts/screenshots/{refresh.sh,capture.mjs} + READMEs — capture into assets/img/ and document the fingerprinting.
  • Favicons stay in static/ unhashed (out of scope — they rarely change; a stale tab icon is harmless).

Verification

Built with the pinned hugo 0.161.1 extended --minify:

  • Clean build, no errors/warnings.
  • Every /img/ URL in the output HTML carries a content hash; 44/44 images published.
  • Light-only pages (notification list/detail) correctly emit no dark <img>.
  • No /img/ references in SCSS/JS; favicons still served.

Passed a 4-lens adversarial review (Hugo correctness, cache efficacy, completeness, repo hygiene) — zero confirmed findings.

Deploy note

Fingerprinting trades per-asset busting for a dependency on HTML freshness: a returning reader picks up a new screenshot only after re-fetching the page HTML (which carries the updated hashed URL). Ensure docs.deploys.app serves .html with a short/revalidating Cache-Control — the hashed /img/* assets themselves can safely be cached immutably/forever.

🤖 Generated with Claude Code

Screenshots lived in static/img/ and were copied to public/img/ verbatim,
so re-capturing a screenshot under the same filename served a stale cached
copy from the browser/CDN. CSS already avoided this via Hugo Pipes
resources.Fingerprint; images did not.

Move static/img/ -> assets/img/ and resolve images through Hugo Pipes so
published URLs carry a content hash (/img/<name>.<hash>.png). Because the
hash is content-based, an unchanged screenshot keeps its URL (cache stays
valid) and only a re-captured one busts — no manual version bumping, and it
holds even under aggressive/immutable asset caching.

- shot.html: resolve src from assets via resources.Get | resources.Fingerprint,
  nil-checked light/dark variants, hard error on a missing image. The author
  API (src="/img/x.png") is unchanged, so the 26 shot invocations across 22
  screenshots in content/ needed no edits.
- navbar.html: logo fingerprinted the same way.
- scripts/screenshots/{refresh.sh,capture.mjs} + READMEs: capture into
  assets/img/ and document the fingerprinting.

Favicons stay in static/ unhashed (out of scope; they rarely change).

Verified with hugo 0.161.1 --minify: clean build, every /img/ URL in the
output carries a content hash, 44/44 images published, light-only pages emit
no dark <img>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9FhPEapaKr4VugQBEdisj
@deploys-app deploys-app Bot temporarily deployed to pr-47 June 23, 2026 11:52 Destroyed
@deploys-app

deploys-app Bot commented Jun 23, 2026

Copy link
Copy Markdown

Preview deleted (PR closed).

@acoshift

Copy link
Copy Markdown
Member Author

Verified end-to-end on the preview deploy (docs-pr-47):

  • All shot screenshots + logo serve 200 at their fingerprinted URLs (/img/<name>.<sha256>.png), correct image/png/image/webp content-type, light + dark variants both present.
  • Hashed assets are served cache-control: public, max-age=31536000, immutable — safe to cache forever precisely because the URL changes when the bytes change.
  • HTML pages are served cache-control: public, max-age=0, must-revalidate (with etag), so a returning reader always revalidates the HTML and picks up the new hashed image URL after a re-capture.

That last point means the deploy-note caveat (HTML must not be cached long/immutable) is already satisfied by the current serving config — no infra change needed for this to work.

@acoshift acoshift merged commit 20483b6 into main Jun 23, 2026
1 check passed
@acoshift acoshift deleted the docs-asset-fingerprint branch June 23, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant