From 387121a97a485a19d73e1c748de255b56a58a1a4 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Mon, 20 Apr 2026 08:26:00 +0000 Subject: [PATCH 1/4] fix(server): support serving mux under any path-rewriting reverse proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #2965. Mux's SPA shell broke when served under a path-rewriting reverse proxy (e.g. Coder path-based apps at `/@user//apps//`, nginx `location /X/ { proxy_pass http://backend/; }`, Traefik StripPrefix, k8s ingress rewrite-target) because: 1. The SPA fallback injected a hardcoded `\`, forcing Vite's relative asset URLs to resolve against the origin root and bypass the proxy (404 on e.g. `__vite-browser-external-*.css`). 2. `index.html` referenced `/manifest.json`, `/favicon.ico`, and `/apple-touch-icon.png` with absolute paths that always bypass `\`. 3. The service worker registered at `/service-worker.js` and cached `/` + `/index.html` with absolute paths. 4. The PWA manifest used absolute `start_url`/icon/shortcut URLs. Prefix-stripping proxies hide the public prefix from the backend by design, so no combination of `X-Forwarded-*` headers is guaranteed to recover it. Instead, emit a **relative** `\` that climbs from the request URL back to the SPA root. The browser resolves relative URLs (including `\`) against the document URL, so a correctly-sized relative climb lands at the SPA root regardless of how the proxy maps the public URL to the backend. This is the same technique code-server uses for its `relativeRoot` helper (see https://github.com/coder/code-server/blob/main/patches/base-path.diff). The change is proxy-agnostic — Coder is just one consumer. ## Changes - `src/node/orpc/server.ts`: inject `\` at startup, then per-request replace the placeholder with a relative climb computed from `req.url`. - `index.html`: make `manifest`, `icon`, and `apple-touch-icon` hrefs relative so they honor `\`. - `src/browser/main.tsx`: register the service worker via `new URL("service-worker.js", document.baseURI)` with a matching scope. `document.baseURI` honors `\`, so a single absolute URL covers every proxy configuration. - `src/browser/contexts/ThemeContext.tsx`: use relative favicon paths when swapping between light/dark themes. - `public/manifest.json`: use relative `start_url`, icon `src`, and shortcut URLs (manifest URLs resolve against the manifest's own URL). - `public/service-worker.js`: cache `./` + `./index.html`. ## Validation - New unit test `SPA shell uses a per-request relative \ that works under any path-stripping proxy` exercises `/`, single-segment, directory-style, and nested SPA routes with query strings. - E2E simulation under four realistic proxy configurations (Coder path-app at root/depth-1/depth-2, generic nginx `/code/` prefix, direct origin at root/depth) confirms every asset — including the exact `__vite-browser-external-*` CSS/JS from the original bug — resolves and loads with HTTP 200. > 🤖 This commit was created with the help of Coder Agents. --- index.html | 6 +-- public/manifest.json | 8 +-- public/service-worker.js | 2 +- src/browser/contexts/ThemeContext.tsx | 4 +- src/browser/main.tsx | 8 ++- src/node/orpc/server.test.ts | 64 +++++++++++++++++++++++- src/node/orpc/server.ts | 70 ++++++++++++++++++++++++--- 7 files changed, 142 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index b3c5c65ee4..3d76070fd3 100644 --- a/index.html +++ b/index.html @@ -8,9 +8,9 @@ /> - - - + + + mux - coder multiplexer