Harden API client against stale-backend HTML fallback

When the running backend is missing a route, the SPA fallback serves index.html
with a 200, and the api helper returned undefined — which crashed pages on
.map/.length (the Delivery pipeline white-screened against an old build). Now a
non-JSON 2xx on an /api call throws a clear 'API is running an older build'
error instead, and PipelinePage defensively coerces its lists to arrays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-17 08:33:59 +03:30
parent c12935ad74
commit d6a0638c59
2 changed files with 14 additions and 3 deletions
+12 -1
View File
@@ -21,7 +21,18 @@ async function request<T>(method: string, url: string, body?: unknown): Promise<
}
const contentType = response.headers.get('content-type') ?? ''
return contentType.includes('application/json') ? ((await response.json()) as T) : (undefined as T)
if (contentType.includes('application/json')) {
return (await response.json()) as T
}
// A non-JSON 2xx on an /api call is almost always the SPA fallback (index.html) — i.e. the route
// doesn't exist on the backend that's running (a stale build). Fail loudly so callers surface it,
// instead of returning undefined and letting the page crash on `.map`/`.length`.
if (url.startsWith('/api') && contentType.includes('text/html')) {
throw new Error(`Unexpected HTML from ${url}. The API is likely running an older build — restart the server.`)
}
return undefined as T
}
export const api = {
+2 -2
View File
@@ -97,8 +97,8 @@ export function PipelinePage() {
api.get<ChangeSummary[]>(`/api/orgboard/change-requests?organizationId=${organizationId}`),
api.get<Division[]>(`/api/orgboard/divisions?organizationId=${organizationId}`).catch(() => []),
])
setRequests(list)
setDivisions(divs)
setRequests(Array.isArray(list) ? list : [])
setDivisions(Array.isArray(divs) ? divs : [])
} catch (err) {
toast.error((err as Error).message)
}