A request that works on your computer is not proof that it will work from the published site. Locally, the frontend and API may share an origin, a Vite proxy may rewrite a path, or the browser may use a URL that exists only on your machine. Production changes the domain, protocol, routes and browser policy. Start by finding the exact request the browser sends and the response it receives.
This guide diagnoses the boundary between a frontend and an API. It does not explain how to build and publish a React application or configure public Vite variables: see the React and Vite Git deployment guide and Vite’s environment-variable documentation. If you are deciding where each component should run, read what hosting an AI-built website needs.
1. Inspect the request in Network
Open your browser developer tools, select Network, repeat one action, and select the failed request. Record its full URL, method, status, Content-Type, initiator and whether an OPTIONS request appears first. Do not share cookies, authorization headers, personal request bodies or keys.
Check the URL’s host. If it contains localhost, 127.0.0.1 or a private address, a visitor’s browser will try to connect to that visitor’s own device or network, not your computer or server. Replace it with an API route reachable from the site or a real HTTPS domain you control. Search the built files for those strings too; a variable named “production” does not change an already-inlined value unless you rebuild the application.
A path such as /api/orders goes to the same origin that served the page. It works only if the platform, web server or production proxy routes that path to your API. Vite’s server.proxy can help during npm run dev, but it is not inserted into the static files created by npm run build. A browser download cannot create a backend service.
2. Separate the preflight from the actual request
For cross-origin calls using JSON, Authorization or other non-simple headers, the browser usually sends OPTIONS first. The server must respond with the allowed origin, methods and headers. If that preflight is rejected, the browser does not send the subsequent main request.
That differs from a simple request, such as some POST requests with Content-Type: text/plain. The server may receive it and change state even when CORS prevents JavaScript from reading the response. CORS controls which responses a browser may read; it does not authenticate a user or ensure that the server did not process a write. Do not use Access-Control-Allow-Origin: * as a patch or as access control. Authenticate and authorize on the backend, validate the body, and apply CSRF protection where relevant. Check Origin according to your design; that header alone is not authentication either.
3. HTTP 200 can contain the wrong page
Inspect the response type and body. If /api/profile returns 200 and text/html containing your application shell, a fallback rule probably sent the home page instead of an API response. Transport succeeded, but your code expected JSON. A 404, 502 or HTML error page on the API route is another clue.
Do not mark an operation as saved just because fetch() did not throw: HTTP error statuses normally still produce a response. Check response.ok and the content type before processing the response. For example:
const response = await fetch('/api/status');
const type = response.headers.get('content-type') || '';
if (!response.ok || !type.includes('application/json')) {
throw new Error(`Unexpected response: HTTP ${response.status}`);
}
const data = await response.json();
This checks only the HTTP status, MIME type and whether the body parses as JSON; it does not validate its fields. Check the object shape and operation result before saying it was saved. It also does not fix routing or create an API.
4. Reproduce the failure in layers
Try the exact backend URL outside the browser with an HTTP client and synthetic data. If it also fails, inspect DNS, TLS, path, process, method and backend logs. If the HTTP client succeeds but Chrome fails, inspect preflight, origin and headers. If Chrome shows 200 with an HTML body, fix server routing or the fallback. Then repeat a read-only operation; avoid testing writes against real data.
On the backend, confirm whether the request arrived and whether it had an effect. A browser console message saying “blocked by CORS” does not prove that no write occurred. Use request IDs and logs that contain no secrets to correlate the result.
What was tested, and what was not
The local experiment for this article ran ten cases using HTTP and Chrome against a synthetic fixture: development proxy, missing proxy in a static build, HTML fallback with status 200, same-origin route, allowed and rejected preflights, a simple write whose result CORS hides, independent write guards, process lifecycle, and routes/assets at two viewport widths. All passed in that fixture. It also verified that a forged Origin could write to the origin-guarded demo, confirming that this control is not authentication.
The fixture is not an integration with the Hostbrid product or a customer account. It does not test authentication, cookies, payments, databases, email, TLS, persistence or customer deployments. The CORS-only demo intentionally allows a synthetic write and must not be copied as a production pattern. The test does not prove indexing or rankings either.
For an application that needs a running process, check the availability and limits of Projects. If you need operating-system control and will operate it yourself, review VPS. Choose based on the process your backend actually needs and confirm plan scope and price before ordering.