diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 23cdafc..c4f4d52 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -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; } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index ea31347..57c7ddb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -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'" > {{ connected ? 'live' : 'reconnecting' }} + ยท {{ socketError }}