From 5598b0cf5b5830d3eecc9c47fe27e3e7abd8a0a4 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Thu, 11 Jun 2026 22:00:57 +0200 Subject: [PATCH] fix(dev): vite proxy to 127.0.0.1 + throttle error notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dev proxy targeted 'localhost', which Node can resolve to IPv6 ::1 that the Docker publish doesn't answer after a container recreate — every /api call failed, spamming 'Network error'. Pin the proxy to 127.0.0.1. Also collapse bursts of connection/5xx failures into one notification per 30s so a brief restart no longer floods the notification center. --- frontend/src/lib/api.ts | 28 ++++++++++++++++------------ frontend/vite.config.ts | 9 ++++++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 6ebd9fe..ea3426a 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -68,6 +68,16 @@ class HttpError extends Error { } } +// Collapse bursts of connection failures (e.g. while the server restarts) into a +// single notification rather than one per failed request. +let lastErrorNotifiedAt = 0; +function notifyErrorThrottled(title: string, message: string): void { + const now = Date.now(); + if (now - lastErrorNotifiedAt < 30_000) return; + lastErrorNotifiedAt = now; + notify({ level: "error", title, message }); +} + async function req(url: string, opts: RequestInit = {}): Promise { const method = opts.method ?? "GET"; let r: Response; @@ -78,22 +88,16 @@ async function req(url: string, opts: RequestInit = {}): Promise { ...opts, }); } catch (e) { - // Network/connection failure — surface it in the notification center. - notify({ - level: "error", - title: "Network error", - message: `Couldn't reach the server (${method} ${url}).`, - }); + notifyErrorThrottled( + "Connection lost", + "Couldn't reach the server — it may be restarting. This will clear once it's back." + ); throw e; } if (!r.ok) { - // Server faults are worth logging; 401/403/404 etc. are handled by callers. + // Server faults are worth surfacing; 401/403/404 etc. are handled by callers. if (r.status >= 500) { - notify({ - level: "error", - title: `Server error ${r.status}`, - message: `${method} ${url}`, - }); + notifyErrorThrottled(`Server error ${r.status}`, `${method} ${url}`); } throw new HttpError(r.status); } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 77d2204..1026675 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,10 +2,13 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; // During `vite dev` (host with Node), proxy API calls to the backend container. +// Use 127.0.0.1 (not "localhost") so Node doesn't resolve to IPv6 ::1, which the +// Docker port publish may not answer — that surfaces as proxy connection failures. +const target = "http://127.0.0.1:8080"; const proxy = { - "/api": "http://localhost:8080", - "/auth": "http://localhost:8080", - "/healthz": "http://localhost:8080", + "/api": target, + "/auth": target, + "/healthz": target, }; export default defineConfig({