Skip to content

Bust caches for unhashed assets so deploys don't break the widgets - #20

Merged
PatrickLaabs merged 1 commit into
mainfrom
ccr-dbe4f8fc-weedkw
Oct 17, 2026
Merged

PatrickLaabs merged 1 commit into
mainfrom
ccr-dbe4f8fc-weedkw

Conversation

@PatrickLaabs

Copy link
Copy Markdown
Owner

Problem

After deploying 0.1.3, the interactive widgets (Chaos Monkey, terminal, load balancing, live manifests) did nothing on patrick.laabs.dev, although they work locally. The only console message was a report-only CSP notice about loading chaos-SXNL27HD.js.

  • The CSP notice is a red herring. The report-only header (script-src 'unsafe-inline' 'unsafe-eval') does not come from the blog; something in front of the cluster adds it. Report-only policies never block anything.
  • The filename is the real clue. chaos-SXNL27HD.js is the chunk from 0.1.2; in 0.1.3 it is chaos-EYWIP36I.js. The browser was running a cached js/main.js from the previous release that imports chunks which no longer exist.
  • Why it was cached: the server sent no Cache-Control and no validators for static files. Embedded files have no modification time, so there was no Last-Modified and no ETag either. A CDN such as Cloudflare then applies its default browser TTL (4h), so a new HTML page ran against an old main.js.

Fix

  • Cache busting: index.html appends ?v=<fingerprint> to styles.css, htmx.min.js and js/main.js. The fingerprint is a SHA-256 over everything under static/, computed once at startup (per request in -dev). A new release therefore always loads its own entry script.

  • Cache headers:

    Response Header
    Hashed bundler chunks (*-XXXXXXXX.js) Cache-Control: public, max-age=31536000, immutable; the name changes whenever the content does
    Other static files Cache-Control: no-cache plus ETag: "<fingerprint>", so caches revalidate cheaply (304)
    HTML Cache-Control: no-cache
    Everything in -dev Cache-Control: no-store

Testing

  • go vet ./... and go build pass.
  • curl checks:
    • HTML references …/main.js?v=b1f21eb1ae77 (likewise for the CSS and htmx);
    • main.js is served with no-cache and the ETag, and If-None-Match returns 304;
    • chaos-EYWIP36I.js is served with immutable;
    • -dev serves static files with no-store.
  • Headless Chromium against the production build: the page loads main.js?v=…, and the Chaos Monkey boots, heals, scales and runs the monkey as before.

After deploying: one hard reload (or purging the CDN cache) clears the stale main.js that browsers already hold. Future deploys won't need it.


Generated by Claude Code

Behind a CDN, browsers kept a stale js/main.js that imported chunks
from the previous release, which no longer exist, so the interactive
widgets silently failed after a deploy. The page now appends a content
fingerprint of static/ to styles.css, htmx.min.js and js/main.js.
Hashed bundler chunks are served as immutable, everything else as
no-cache with an ETag, and the HTML as no-cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hwr9dH2APEpeX6fBPghR5g
@PatrickLaabs
PatrickLaabs merged commit 88bd630 into main Oct 17, 2026
1 check passed
@PatrickLaabs
PatrickLaabs deleted the ccr-dbe4f8fc-weedkw branch October 17, 2026 20:16
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.

2 participants