fix(frontend): emit relative asset paths so SPA loads under any subpath (#1195)

Vite's default base of '/' baked absolute asset URLs into the built
  index.html (/assets/..., /manifest.json, /img/..., /sw-register.js),
  so any path-prefixed reverse proxy (Traefik, nginx subpath, Cloudflare
  Tunnel with path routing) served the SPA as a blank white page — the
  browser requested assets from the host root and got HTML or text/plain
  back, triggering MIME-mismatch errors on every stylesheet/script.

  Set base: '' in vite.config.ts so the HTML transform emits relative
  URLs everywhere. Update public/sw-register.js to register('sw.js')
  (relative) so SW scope auto-pins to whatever subpath the document
  loaded from.

  Out of scope: API_BASE in client.ts is still absolute. The supported
  HA embedding path remains Webpage panel + TRUSTED_FRAME_ORIGINS, not
  HA Ingress (subpath-aware SPA bootstrapping has too many failure modes
  around PWA scope, push subscriptions, and deep-link reloads to take on
  in core). Documented explicitly in docker.md.

  Reported by @Spegeli, follow-up to #1167.
This commit is contained in:
maziggy
2026-05-03 07:33:40 +02:00
parent 875d80ad3c
commit d6a3139397
7 changed files with 16 additions and 8755 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ if ('serviceWorker' in navigator) {
});
} else {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
navigator.serviceWorker.register('sw.js')
.then((registration) => {
console.log('SW registered:', registration.scope);
})
+4
View File
@@ -74,6 +74,10 @@ function serveGcodeViewer() {
}
export default defineConfig({
// Empty base emits relative asset URLs (./assets/... instead of /assets/...)
// so the built SPA loads correctly when served at any subpath — HA Ingress,
// nginx/Traefik path prefix, Cloudflare Tunnel path routing, etc. (#1195).
base: '',
plugins: [react(), serveGcodeViewer()],
build: {
outDir: '../static',