← Blog · Published Oct 19, 2026 · by the Vibefixr team

Your app calls localhost or hits a CORS error after deploy: here’s why

Quick answer: Why does my app call localhost or get a CORS error after I deploy it?

Usually it’s one of two separate problems. Code written during development often hard-codes http://localhost as the API address, so the live site asks each visitor’s own computer for data. Or the API is reachable but doesn’t send an Access-Control-Allow-Origin header for your live domain, so the browser refuses to hand the response to your code. Put the API URL in an environment variable and rebuild, and allow your exact production origin on the server.

Everything works on your laptop. You deploy, open the live site, and the data never loads. The console says something like Access to fetch at 'https://api.yourapp.com/items' from origin 'https://yourapp.com' has been blocked by CORS policy, or the Network tab shows requests going to http://localhost:3001. This is very common in apps built with Cursor or any AI tool, where the frontend and an API were written and tested side by side on one machine.

Not the same as login sending users to localhost. If it’s the redirect after signing in that lands on localhost, that’s a Supabase Auth setting: see Google login redirects to localhost. This guide is about your app’s own API calls.

Two problems that look alike

Open your live site, press F12, go to the Network tab and reload. Click the failing request and look at its URL.

What you seeWhat it means
The request URL starts with http://localhost or 127.0.0.1Problem 1: the live code still points at your development API
The URL is your real API, the console says “blocked by CORS policy” or “No ‘Access-Control-Allow-Origin’ header”Problem 2: the server doesn’t allow your live domain
A failed OPTIONS request right before your real requestProblem 2, at the preflight step
Firefox says “CORS request did not succeed”Usually not CORS at all: the request never got an answer (see below)

Problem 1: your code still points to localhost

Why it works on your machine

localhost always means “this computer”, and in the browser that’s the visitor’s computer. On your laptop your API really is running there, so it works. For every visitor to your live site, there’s nothing on their localhost, so the request fails. Search your project for localhost and 127.0.0.1: an AI tool may have written the address straight into a fetch call or an API client file.

The short fix: put the API address in an environment variable

Read the base URL from a variable and set a different value for development and production:

// Vite projects
const API = import.meta.env.VITE_API_URL;
fetch(`${API}/items`);

// Next.js
const API = process.env.NEXT_PUBLIC_API_URL;

Then set VITE_API_URL (or NEXT_PUBLIC_API_URL) to your production API address in your host’s environment settings, and rebuild. Both tools bake these values into the JavaScript at build time: Vite statically replaces import.meta.env values during the build, and Next.js inlines NEXT_PUBLIC_ values when you run next build, after which the app no longer responds to changes in them. Changing the variable without a new build changes nothing.

If the API is served from the same domain as the site (for example /api/items on Next.js or behind a host rewrite), a relative URL like fetch('/api/items') avoids both the variable and CORS entirely.

These variables end up in public JavaScript. Use them for addresses, never for secrets. See what to do if a secret key leaked.

The Vite dev proxy only exists in development

Many projects use server.proxy in vite.config so that /api calls are forwarded to a local backend. Vite’s server options docs say these options only apply to the dev server. After vite build, there’s no proxy: /api calls go to whatever host serves your static files, and that host has to forward them itself (or the app has to call the real API URL).

Problem 2: CORS

What CORS is, in one paragraph

Browsers stop JavaScript on one origin (scheme, domain and port, like https://yourapp.com) from reading responses from another origin unless that other server says it’s allowed. The server says so with the Access-Control-Allow-Origin response header. That’s why a CORS error is fixed on the server, not in your frontend code, as MDN’s CORS guide explains. It’s also why the same request works in Postman or curl: CORS is enforced by browsers.

Preflight: the OPTIONS request that fails first

For many requests, the browser first sends an OPTIONS “preflight” request to ask permission. A JSON body (Content-Type: application/json), an Authorization header or methods like PUT and DELETE all trigger one. The server has to answer that OPTIONS request with the right headers (Access-Control-Allow-Origin, -Methods, -Headers), or the real request is never sent. Backends that only define GET and POST routes often return 404 or 405 for OPTIONS.

The short fix: allow your exact production origin

Configure your API to return your live origin, for example with the common cors middleware in an Express server:

import cors from "cors";
app.use(cors({
  origin: ["https://yourapp.com", "http://localhost:5173"],
  credentials: true, // only if you send cookies
}));

Supabase Edge Functions answer CORS themselves

If the failing call goes to /functions/v1/…, the function itself has to handle the preflight and add CORS headers to every response. Supabase’s CORS guide shows two ways: the withSupabase wrapper, which handles preflight for you, or importing corsHeaders from npm:@supabase/supabase-js@^2/cors (supabase-js 2.95.0 and later) and returning them on the OPTIONS response, the normal response and error responses. A function that works locally but fails live can also be missing its secrets: see Edge Function works locally, fails in production.

Not sure which of these is your problem? The free health check looks at your live app in seconds, or request a free scan and we’ll tell you what we find.

Things that make CORS errors misleading

Never “fix” it with a public CORS proxy or by switching off security

Routing requests through someone else’s “CORS proxy” sends your users’ data and tokens through a server you don’t control. Browser extensions or flags that disable CORS only change your own browser, so your users still see the error. mode: "no-cors" in fetch doesn’t help either: MDN notes the response becomes opaque, so your code can’t read it. If you don’t control the API, the sanctioned route is a small proxy on your server (a Next.js route handler, a serverless function, a Supabase Edge Function) that calls the API and returns the data.

When it’s more than one endpoint

One API and one domain is a short fix. It gets harder when the app calls several services, has a custom domain plus preview URLs, sends cookies across subdomains, or the AI tool has spread hard-coded URLs through dozens of files. Then you want every call mapped once, rather than fixing them one error at a time.

Several APIs, several domains, still blocked?

The $49 Audit is a security & stability review with a full report in 24 hours and copy-paste fix prompts for your AI tool. The $199 Fix sorts up to 5 issues as a pull request with tests within 72 hours. Not fixed? Full refund. Covers the issues you approve before we start.

Get the $49 auditFix my app ($199)Free health check

FAQ

Is a CORS error a bug in my frontend?

Almost never. The server decides which origins may read its responses, so the fix is on the API side: return your exact live origin in Access-Control-Allow-Origin and answer the OPTIONS preflight.

Can I just set Access-Control-Allow-Origin to *?

Not if you send cookies or other credentials: browsers reject the wildcard for credentialed requests. MDN also advises against * for any private API. Name the origins you trust.

Why does it work in Postman but not in the browser?

CORS is enforced by browsers. Postman and curl don’t apply it, so they get the response even when a browser would block it.

I set the variable on Vercel or Netlify but the app still calls localhost. Why?

Vite and Next.js put VITE_ and NEXT_PUBLIC_ values into the JavaScript at build time. Trigger a new build after changing them, and check the name matches what the code reads.

Does this happen with Lovable or Bolt apps too?

Yes, whenever the app calls its own API or an outside service from the browser. Supabase Edge Functions are a common case: they must answer the preflight and send their own CORS headers.

Related guides

Launching soon? Run through our launch checklist for AI-built apps: 14 checks, each linked to the full guide.

Sources: MDN: CORS · MDN: Access-Control-Allow-Origin · MDN: CORS errors · Vite: env variables · Vite: server.proxy · Next.js: environment variables · Supabase: Edge Functions CORS

Information here is checked against official docs at the time of writing. Platforms change, so verify against the linked sources.