CORS works locally but fails in production

If the same browser request works on localhost but fails after deployment, compare what changed between the two environments before changing your fetch code. CORS decisions depend on the exact requesting origin and the response the browser actually receives.

Why production can behave differently

An origin is the combination of scheme, host, and port. http://localhost:3000, https://example.com, and https://www.example.com are different origins.

A server that allows your localhost origin can still reject the production site. Production may also add a CDN, reverse proxy, redirect, authentication layer, or different API URL that changes the response before the browser sees it.

Check these first

  1. Confirm the exact production page origin shown by location.origin.
  2. Make sure that exact origin is included in the API's CORS allowlist.
  3. Check for differences such as www vs non-www, preview domains, HTTP vs HTTPS, or a non-default port.
  4. Verify the production build is calling the API URL you expect, not a stale environment variable or different backend.
  5. Inspect redirects, CDN responses, reverse proxies, and error responses for missing or changed CORS headers.
  6. If the request preflights, inspect the production OPTIONS response separately.
  7. If credentials are involved, verify the credentialed CORS response and cookie settings match the production setup.

Compare from the production page

Open DevTools on the deployed page that is failing and run this controlled request from its Console:

fetch('https://cors.anotherexample.com/api/cors/lab?allowOrigin=echo')
  .then(r => r.json())
  .then(console.log)
  .catch(console.error);

AnotherExample reflects the requesting Origin for this controlled test. If this request works from the production page while your real API request fails, focus first on the real API's production CORS path, allowlist, proxy, redirect, or deployment configuration.

Common production-only causes

The most common pattern is simple: localhost is allowed but the deployed origin is not. Other causes include a production proxy stripping headers, an API gateway handling OPTIONS differently, a redirect to another host, a deployment-specific authentication response without CORS headers, or a production environment variable pointing to a different API.

Do not assume the frontend code is the problem just because the error appears only after deployment. Compare the actual request URL, Origin, preflight, and final response between local and production.

Related CORS guides

Blocked by CORS policy: what to check →

CORS preflight / OPTIONS request failed →

No Access-Control-Allow-Origin header →

Still stuck?

Compare your production request in the CORS Debugger →

Paste the production browser error into the Error Explainer →

Explore controlled CORS scenarios in the Playground →