How to Debug
The failure modes you'll actually hit when a React app can't reach its API, and how to tell them apart quickly.
Symptom: requests in the Network tab go to localhost:10001 (or some other stale host)
even though the app is deployed to a real domain.
A hardcoded or stale absolute URL got baked into the bundle — usually because
VITE_API_BASE_URL was left pointing at a dev value (like
.env.local's https://localhost:10001) in the file that was actually used for
the build.
Open devtools → Sources (or Network → click the request → look at Request URL), or search the built output directly:
grep -r "localhost:10001" dist/
If it's present in dist/, it's baked in — no amount of restarting the server will fix
it. You must fix the .env file used for that build and rebuild.
.env
Symptom: you changed VITE_API_BASE_URL (or any VITE_* value) on the server
or in the repo, restarted the web server serving the static files, and... nothing changed.
This is expected, not a bug. React/Vite never re-reads .env files at runtime — see
Chapter 1.3. Restarting a
static file server just re-serves the same already-built files.
Rebuild (vite build --mode <environment>) and redeploy the new
dist/ output.
/api/api/... in request URLs
Symptom: requests 404, and the URL in the Network tab looks like
https://qa.grippingly.com/api/api/v1/atlas/forms/list — the /api segment
appears twice.
Something is prepending an extra /api in front of an endpoint constant that already
starts with /api/v1/.... This exact bug shipped briefly in
calculateApiUrl()'s default-empty case before being caught in review — see
Chapter 2.4 for the full
incident writeup. If you ever see this pattern again, check whether
VITE_API_BASE_URL was explicitly set to /api (it shouldn't be — leave it
empty for inline deployments) or whether the shared URL-resolution function has regressed.
Leave VITE_API_BASE_URL empty for inline deployments, and never add a default path
suffix inside calculateApiUrl() — path composition belongs to the endpoint constants and
buildUrl() alone.
Symptom: browser console shows something like "has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present".
Likely causeAn independent deployment — see Chapter 1.6 — where the API's CORS configuration doesn't (yet) list the frontend's exact origin.
How to confirmCheck the Network tab for the failed request's response headers. A missing
Access-Control-Allow-Origin header (or one naming a different origin than the page you're
on) confirms it.
Add the frontend's exact origin (scheme + host + port) to the API's CORS allowed-origins configuration.
/api/* in inline local dev
Symptom: running vite dev locally, every API call 404s, even though the API itself
works fine when hit directly.
The Vite dev server proxy in vite.config.ts either isn't configured for the path being
requested, or the API isn't actually running on the port the proxy targets.
Check the server.proxy block:
server: {
port: 6005,
proxy: {
'/api': {
target: 'https://localhost:10001',
changeOrigin: true,
secure: false
}
}
}
If the request path isn't under /api, or the API isn't listening on
localhost:10001, the proxy has nothing to forward to.
calculateApiUrl() actually resolved to
When in doubt, don't guess — log it. Open the browser console on the deployed page and check what the
app itself believes its API base URL is (FlowStudio's App.tsx already logs this outside
dev mode: console.log('[Auth Config] API Base URL:', apiBaseUrl)), or evaluate it
directly if the module is reachable from the console.
Compare the logged value against what you expect for the deployment model in use (bare origin for
inline/empty, absolute URL for independent). If it doesn't match, trace back to which
.env.* file was actually used for that build.