Stop caching index.html and speed up socket reconnect
CI/CD / backend (pull_request) Successful in 5m41s
CI/CD / frontend (pull_request) Successful in 5m18s
CI/CD / build (pull_request) Skipped
CI/CD / deploy (pull_request) Skipped
CI/CD / cleanup (pull_request) Skipped

Phones offer no easy hard-refresh, so a cached index.html kept serving an old
bundle after a deploy (the live feed showed "reconnecting"). index.html and the
SPA catch-all now send Cache-Control: no-store, while hashed /assets stay
immutable. Also retry the first socket reconnect after 1s (was 2s) and surface
the close code in the header so a failure is actionable on a phone.
This commit is contained in:
matitos committed 2026-09-19 23:52:54 +00:00
1 parent 6df44520fa
commit c63753e592
3 files changed
+33 -6

No files matched your search

+15
View File
@@ -4,7 +4,22 @@ server {
root /usr/share/nginx/html;
index index.html;
# Hashed Vite assets are immutable; cache them hard.
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
# The HTML entry point must never be cached, or a phone (which offers no
# easy hard-refresh) keeps loading an old bundle after a deploy. The
# catch-all serves index.html for client-side routes, so it gets the same
# no-store header.
location = /index.html {
add_header Cache-Control "no-store";
}
location / {
add_header Cache-Control "no-store";
try_files $uri $uri/ /index.html;
}
+10 -1
View File
@@ -36,6 +36,7 @@ const events = ref([])
const equityPoints = ref([])
const busy = ref(false)
const connected = ref(false)
const socketError = ref('')
let socket
let pollTimer
@@ -132,8 +133,15 @@ onMounted(async () => {
])
socket = api.openSocket({
onMessage: handleMessage,
onOpen: () => (connected.value = true),
onOpen: () => {
connected.value = true
socketError.value = ''
},
onClose: () => (connected.value = false),
onError: (code) => {
if (code === 1008) socketError.value = 'rejected by the server (token/auth)'
else socketError.value = `socket closed (code ${code})`
},
})
pollTimer = setInterval(refreshAll, 5000)
})
@@ -161,6 +169,7 @@ onUnmounted(() => {
:class="connected ? 'bg-emerald-400' : 'bg-slate-500'"
></span>
{{ connected ? 'live' : 'reconnecting' }}
<span v-if="!connected && socketError" class="text-amber-300">· {{ socketError }}</span>
</span>
</div>
<div class="text-xs text-slate-400">
+8 -5
View File
@@ -26,7 +26,7 @@ export const getPresets = () => http.get('/presets').then((r) => r.data)
export const createPreset = (payload) => http.post('/presets', payload).then((r) => r.data)
export const deletePreset = (id) => http.delete(`/presets/${id}`).then((r) => r.data)
export function openSocket({ onMessage, onOpen, onClose }) {
export function openSocket({ onMessage, onOpen, onClose, onError }) {
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'
const url = `${proto}://${window.location.host}/api/ws`
let socket
@@ -36,9 +36,9 @@ export function openSocket({ onMessage, onOpen, onClose }) {
const schedule = () => {
if (closed) return
// Back off from 2s up to 30s so a persistently failing socket (e.g. auth)
// does not hammer the server, while a brief blip still reconnects fast.
const delay = Math.min(2000 * 2 ** attempt, 30000)
// Retry at 1s, then back off up to 30s so a persistently failing socket
// does not hammer the server while a brief blip recovers immediately.
const delay = Math.min(1000 * 2 ** attempt, 30000)
attempt += 1
retryTimer = setTimeout(connect, delay)
}
@@ -51,8 +51,11 @@ export function openSocket({ onMessage, onOpen, onClose }) {
onOpen?.()
}
socket.onmessage = (event) => onMessage?.(JSON.parse(event.data))
socket.onclose = () => {
socket.onclose = (event) => {
onClose?.()
// 1008 = policy violation (token rejected); 1006 = abnormal close, which
// is what an auth/proxy rejection looks like to the browser.
onError?.(event.code, event.reason)
schedule()
}
socket.onerror = () => socket.close()