fix(webui): drop static-asset cache from 1h to 60s

The 1h max-age set in Phase A caused a cache-vs-deploy mismatch when
Phase B refactored config-manager.js to lazy-load admin-overview.js et
al. The new index.html no longer eager-loads those scripts, but
browsers with the cached (pre-Phase-B) config-manager.js didn't do the
lazy-load either — so AdminOverview / AdminSystem / etc. were
undefined and the admin tools rendered 'failed to load' errors.

60s is the right balance: rapid in-session clicks skip the network
round-trip, but a deploy is visible within a minute. ETag-based 304s
still keep the per-request cost tiny when nothing changed.

Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-26 22:55:35 +01:00
parent 734ca2940c
commit bbae95b504

View File

@ -30,14 +30,16 @@ function noStore(req, res, next) {
} }
// Static-asset options: // Static-asset options:
// - 1h maxAge + ETag on JS/CSS/icons so repeated nav skips a network // - 60s maxAge + ETag on JS/CSS/icons. Long enough that rapid in-session
// round-trip per file. ~25 script tags × ~5ms RTT each adds up otherwise. // clicks skip the network round-trip, short enough that a deploy is
// - HTML files get Cache-Control: no-cache (still uses ETag, so revalidation // visible within a minute. Originally tried 1h but that caused stale
// is cheap, but new deploys land immediately without waiting for cache // cached JS to reference things the new HTML no longer loaded (the
// expiry — the SPA shell is the file most likely to change between deploys). // Phase-B lazy-load refactor changed who loads which script).
// - HTML files get Cache-Control: no-cache (always revalidates via ETag,
// so new deploys land immediately — the SPA shell changes most often).
// - dotfiles='ignore' so .auth.json is never served. // - dotfiles='ignore' so .auth.json is never served.
const staticOptions = { const staticOptions = {
maxAge: '1h', maxAge: '60s',
etag: true, etag: true,
dotfiles: 'ignore', dotfiles: 'ignore',
setHeaders: (res, filePath) => { setHeaders: (res, filePath) => {