CORS error only in production, but it works on localhost
What the browser console showsAccess to fetch at 'https://api.example.com/items' from origin 'https://www.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
# Firefox:
Reason: CORS header 'Access-Control-Allow-Origin' missing
Your API's list of allowed websites still only has http://localhost:3000. Add your real domain (exactly, scheme included) and redeploy the API.
Why it happens
When your page calls an API on a different domain, the browser asks the API whether that page's origin is allowed. If the response doesn't include an Access-Control-Allow-Origin header that matches, the browser blocks it.
It works locally because your allowlist says localhost. In production the origin is https://www.example.com, which isn't on the list. Other common causes: www versus no www, http versus https, a trailing slash in the configured origin, cookies sent with a wildcard origin, or the API crashing (error responses often have no CORS headers, so a 500 shows up as a CORS error).
Fix it by hand
- Look at the failing request. In the browser's Network tab, select the request. Note the
Originrequest header and the response status. If the status is 404 or 500, fix that first: the CORS message may be a side effect. - Allow your exact production origin. Origins are scheme plus host plus port, with no path or trailing slash. With the Express
corspackage, read them from an environment variable so each environment can differ:const allowed = (process.env.CORS_ORIGINS || '').split(','); // CORS_ORIGINS=https://example.com,https://www.example.com app.use(cors({ origin: allowed, credentials: true })); - If you send cookies or auth headers, don't use
*. Browsers refuse a wildcard with credentials, and MDN warns against*for private APIs. Return the specific origin and addVary: Originif you set headers by hand. - Handle preflight requests. Requests with custom headers or methods like
PUTandDELETEsend anOPTIONSrequest first.app.use(cors(...))handles these for all routes. In Next.js, set the headers in the route handler or inproxy.tsand answerOPTIONS. - Redeploy the API (not just the frontend), then hard-refresh the site and retry.
Stop it happening again
- Serve the API from the same domain as the site where you can (for example under
/api). Same-origin requests need no CORS at all. - Keep allowed origins in an environment variable per environment, and add the production domain before the first deploy.
- Never ship
origin: '*'on an API that uses cookies or tokens.
Broke After Deploy $9
Broke After Deploy flags CORS configs that only allow localhost, frontend calls that still point at localhost, and http:// calls from an https site, before you deploy.
- Checks your project for 37 known causes of "works locally, breaks in production" and runs your production build.
- Paste the error and it finds the cause in your code, then lists what will break next.
- Verdict first. Every finding shows the file and line, what it breaks, and the fix as a diff that is applied only with your OK.
Broke After Deploy: how it works, with examples · Install guide
Works with Claude Code, Claude.ai, Cursor, Codex and other agents that read SKILL.md files. No installs, no accounts, works offline. No-questions refund.