Skip to content

Version static CSS/JS URLs so updates aren't served from browser cache - #224

Merged
Brandon-Haney merged 2 commits into
StudioNirin:mainfrom
Brandon-Haney:pr/static-cache-busting
Oct 6, 2026
Merged

Brandon-Haney merged 2 commits into
StudioNirin:mainfrom
Brandon-Haney:pr/static-cache-busting

Conversation

@Brandon-Haney

Copy link
Copy Markdown
Collaborator

Browsers keep /static files across updates, so after an upgrade the new HTML can run against the previous release's app.js or custom.css until the user clears their cache. DeLo1585 ran into this while testing #223 (a new template with an old cached app.js), and any release that changes CSS or JS can hit it, including #218 and #219.

Change

  • New static_url() template global in web/config.py that returns /static/<path>?v=<hash>, where the hash comes from the file's contents. It is cached and recomputed only when the file's modification time changes.
  • Every CSS and JS include in the templates now uses it (base.html, login/link, setup, and the settings pages that load path-browser.js / settings_search.js). Images and the favicon are unchanged.
  • Using the file's own contents rather than the app version or GIT_COMMIT means it works the same on Docker images, git checkouts and dev builds, and a file's URL only changes when that file actually changes.

Tests

tests/test_static_url.py covers the hash, a changed file getting a new URL, an unchanged file keeping its URL, a missing file falling back to the plain URL, and the real static files resolving. It also fails if any template references a CSS/JS file under /static directly, so new includes can't skip it.

How to test

  1. Open the Web UI and view the page source: CSS and JS links end in ?v= followed by a short hash.
  2. Edit web/static/css/custom.css (any change), reload the page, and the custom.css link has a new ?v= value and the change shows without clearing the browser cache.
  3. Links for files you didn't touch keep the same ?v= value.

Browsers keep /static files across updates, so after an upgrade a new
template could run against the previous release's app.js or custom.css.
Every CSS/JS include now goes through a static_url() template global that
appends a hash of the file's contents, so the URL changes exactly when the
file does. Works the same for Docker images, git checkouts and dev builds.

A test fails if a template references a CSS/JS file under /static directly.
Several test modules replace web.config in sys.modules with a MagicMock at
import time, so a test that imports web.config only gets the real module
if it happens to be collected first. static_url() now lives in
web/static_assets.py, which has no app imports, and its tests load that
module directly. web.config still registers it as a template global.
@Brandon-Haney
Brandon-Haney merged commit cec2c78 into StudioNirin:main Oct 6, 2026
2 checks passed
@Brandon-Haney
Brandon-Haney deleted the pr/static-cache-busting branch October 6, 2026 21:28
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