JS assets lack cache busters, causing stale files after updates #204

Closed
opened 2026-10-04 23:04:09 +02:00 by adam · 0 comments
Owner

Problem

All JavaScript assets are served with static paths and no cache invalidation mechanism:

<script src="/assets/js/theme.js" defer></script>
<script src="/assets/js/background.js" defer></script>
<script type="module" src="/assets/js/app/index.js"></script>

The same pattern appears in charts.erb, federation.erb, and node_detail.erb. Browsers cache these files aggressively. After a deployment, users continue running the old JS until they hard-refresh manually.

Root cause

Sinatra serves public/ as static files with no fingerprinting. The templates embed asset URLs as bare paths with no version query parameter.

APP_VERSION is already resolved at startup from git describe --tags --long --abbrev=7 (see application/identity.rb) and is available in the template context, but it is never appended to asset URLs.

Proposed fix

Add an asset_url(path) helper in html_helpers.rb that appends ?v=<APP_VERSION>:

def asset_url(path)
  "#{path}?v=#{PotatoMesh::Application::APP_VERSION}"
end

Replace all static asset references in templates with the helper:

<script src="<%= asset_url('/assets/js/theme.js') %>" defer></script>
<script src="<%= asset_url('/assets/js/background.js') %>" defer></script>
<script type="module" src="<%= asset_url('/assets/js/app/index.js') %>"></script>

This is a one-liner per include, requires no build step change, and uses version data already computed at boot. After each release the URL changes, browsers fetch fresh files.

CSS assets (e.g. base.css) and any inline import statements in ES module scripts (e.g. in charts.erb) should get the same treatment.

## Problem All JavaScript assets are served with static paths and no cache invalidation mechanism: ```erb <script src="/assets/js/theme.js" defer></script> <script src="/assets/js/background.js" defer></script> <script type="module" src="/assets/js/app/index.js"></script> ``` The same pattern appears in `charts.erb`, `federation.erb`, and `node_detail.erb`. Browsers cache these files aggressively. After a deployment, users continue running the old JS until they hard-refresh manually. ## Root cause Sinatra serves `public/` as static files with no fingerprinting. The templates embed asset URLs as bare paths with no version query parameter. `APP_VERSION` is already resolved at startup from `git describe --tags --long --abbrev=7` (see `application/identity.rb`) and is available in the template context, but it is never appended to asset URLs. ## Proposed fix Add an `asset_url(path)` helper in `html_helpers.rb` that appends `?v=<APP_VERSION>`: ```ruby def asset_url(path) "#{path}?v=#{PotatoMesh::Application::APP_VERSION}" end ``` Replace all static asset references in templates with the helper: ```erb <script src="<%= asset_url('/assets/js/theme.js') %>" defer></script> <script src="<%= asset_url('/assets/js/background.js') %>" defer></script> <script type="module" src="<%= asset_url('/assets/js/app/index.js') %>"></script> ``` This is a one-liner per include, requires no build step change, and uses version data already computed at boot. After each release the URL changes, browsers fetch fresh files. CSS assets (e.g. `base.css`) and any inline `import` statements in ES module scripts (e.g. in `charts.erb`) should get the same treatment. <!-- github-sync-id:4700963930 -->
adam closed this issue 2026-10-04 23:04:09 +02:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/potato-mesh#204