How to Fix CORS Errors in AI-Generated Apps
CORS is a browser mechanism, not a server bug. That's why Postman works but your deployed app doesn't. This is a step-by-step guide to fix it properly.
CORS is a browser mechanism. Whenever your app tries to send a request to a different host, the browser asks the server if it’s allowed to do so. It’s not a bug in your code — it’s a security layer built into every browser. The problem is that most people were never taught how it works.
AI-generated code plus public deployment is where most people get burned. On localhost everything worked because the frontend and backend lived at the same address. After deployment — they’re on different domains and the browser blocks the requests.
This article is a practical step-by-step guide. I explain what CORS actually is, why AI tools make it worse, and how to fix it once and for all — with code you can copy and use immediately.
What CORS Actually Is
CORS (Cross-Origin Resource Sharing) is a browser security mechanism, not a server error. Here’s how it works: whenever your application tries to send an HTTP request to a different origin (a different domain, port, or protocol), the browser first asks the target server whether this is allowed.
On localhost, your frontend and backend usually run at the same address — for example http://localhost:3000. That’s same-origin — same domain, same port, same protocol. The browser sees no issue because the request goes to the same host. This is why everything works during development.
After deployment, your frontend lives at https://myapp.com and your backend at https://api.myapp.com or an entirely different domain. That’s cross-origin. And this is where the browser steps in.
Before the browser sends your actual request (POST, PUT, DELETE), it first sends a so-called preflight request — an OPTIONS request to the same URL. It’s essentially asking: “Hey server, is the frontend from domain X allowed to send POST requests with these headers?” If the server doesn’t respond with the right CORS headers, the browser blocks the request before it’s even sent.
The key thing to understand: CORS is a browser mechanism. Postman, curl, server-to-server requests — none of them enforce CORS. That’s why your API can work perfectly in Postman while throwing errors in the browser. It’s not an API bug — it’s the browser doing its job.
The CORS Cascade: How AI Makes It Worse
Here’s the typical scenario: you see a CORS error in the console. You tell AI “fix CORS.” AI changes something in the server code. The CORS error disappears — but now something else is broken. You tell AI “fix that.” AI rewrites another part of the code. And that’s how the cascade begins.
I’ve seen this firsthand with clients: letting AI “fix CORS” rewrote server code and broke imports, database queries, and response shapes. Three days to undo what AI did in seconds.
The problem is fundamental: AI doesn’t understand the boundary between frontend and backend. It doesn’t know which code runs in the browser and which runs on the server. When you ask it to fix CORS, AI often modifies the frontend (adds mode: 'no-cors' to fetch) or changes server code in ways that break everything else.
The worst things AI does:
- Adds
Access-Control-Allow-Origin: *everywhere — a wildcard instead of specific domains. Dangerous in production. - Doesn’t handle
OPTIONSrequests — the preflight goes unanswered, so the browser blocks everything. - Adds
mode: 'no-cors'to fetch on the frontend — this hides the problem, not fixes it. The response becomes “opaque” and you can’t read any data. - Changes working code while trying to fix something it doesn’t understand — that’s the cascade.
When things break, people react naturally: they keep prompting. “Fix this.” “Now that doesn’t work.” “Revert the last change but keep the CORS fix.” Each successive prompt makes things worse because AI doesn’t remember why the code looked the way it did.
Step-by-Step Fix
Step 1: Check the real error
Most people look at the CORS error in the browser console and assume it’s a CORS problem. But often the real error is elsewhere. Open the Network tab in DevTools, not Console.
Find the failed request. Click on it and check the response code. If you see 500 Internal Server Error, your server crashed — and because it crashed, it didn’t add CORS headers to the response. The browser sees the missing Access-Control-Allow-Origin header and shows a CORS error, but the real problem is a server error.
If the endpoint errors out (500), Access-Control-Allow-Origin usually won’t be added. Fix the server error, and the “CORS error” will disappear on its own.
Step 2: The RIGHT way to configure CORS (Express.js)
Here’s what a CORS middleware should look like on an Express.js server. Notice that it sets specific domains instead of wildcards, handles the OPTIONS request, and sets credentials:
const allowedOrigins = ['https://yourapp.com', 'https://app.yourapp.com'];
function corsMiddleware(req, res, next) {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods',
'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers',
'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials',
'true');
}
if (req.method === 'OPTIONS')
return res.status(204).end();
next();
}
Step 3: What AI generates (wrong)
And here’s what AI typically generates when you ask it to “fix CORS.” It looks simple, but it’s full of problems:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
// No OPTIONS handling
// Wildcard in production
// No credentials support
// No Allow-Methods or Allow-Headers
The differences are critical: the wildcard * means any website in the world can send requests to your API. No OPTIONS handling means preflight requests won’t work. No Allow-Credentials means cookies and tokens won’t be sent. This isn’t “fixed” — it’s an open security hole.
Step 4: CORS in Supabase Edge Functions
If you’re using Supabase Edge Functions (Deno), CORS must be handled manually in each function — or, even better, centralized in a single helper file. Here’s the correct pattern:
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://yourapp.com',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers':
'Content-Type, Authorization',
};
Deno.serve(async (req) => {
if (req.method === 'OPTIONS')
return new Response(null, {
status: 204,
headers: corsHeaders
});
// Your logic here
const data = { message: 'Hello' };
return new Response(
JSON.stringify(data),
{
headers: {
...corsHeaders,
'Content-Type': 'application/json'
}
}
);
});
Step 5: Centralize CORS configuration
One file, one place. CORS configuration should live in one place across your entire project — not scattered across every endpoint and every function. Create a cors.js (or cors.ts) file and import it everywhere it’s needed.
At the same time, lock down your JSON response shape — every endpoint should return data in the same format. Keep your entry file tiny — it should only import middleware and start the server. All business logic goes into separate files.
This way, when you need to change allowed origins or add a new header, you do it in one place, and the change works everywhere. Lack of centralization is one of the main reasons AI apps work locally but fail in production.
Step 6: Debug tricky cases
Sometimes everything looks correct and CORS still doesn’t work. Here are the most common traps:
- URL mismatches:
https://myapp.comis not the same ashttps://www.myapp.com. Watch out for trailing slashes — the browser sends theOriginheader without a trailing slash, so if your allowlist includeshttps://myapp.com/, the string comparison will fail. no-corsmode in fetch: If someone (or AI) addedmode: 'no-cors'to a fetch request, the browser won’t throw an error — but the response will be “opaque.” You can’t read the status, headers, or body. This isn’t a fix — it’s sweeping the problem under the rug.- Mixing HTTP and HTTPS:
http://andhttps://are different origins. If your frontend is on HTTPS and your backend on HTTP, CORS will block the request, and on top of that the browser may block mixed content. - Dev proxy vs. production: Many frameworks (React, Vue, Next.js) use a proxy in development mode that masks cross-origin requests. In production, that proxy doesn’t exist — and suddenly CORS explodes. That’s why CORS errors only show up after deployment.
What AI Tools Get Wrong About CORS
AI coding tools — Cursor, Claude Code, Copilot, and others — are incredibly effective at generating code. But CORS is one area where they consistently fail. Why? Because CORS is a configuration and architecture problem, not a code logic problem. AI excels at writing logic. It’s poor at understanding deployment context.
Here are the recurring patterns I see with clients:
- AI edits server code when it should only touch frontend — and vice versa. It doesn’t understand which code runs where. It often proposes changes on both sides, creating inconsistencies.
- AI uses wildcard
*in production — which is not only dangerous but makes it impossible to use credentials (cookies, tokens) in requests. - AI doesn’t handle preflight
OPTIONSrequests — which means POST, PUT, and DELETE requests will be blocked by the browser, even if CORS headers are set correctly on regular responses. - AI doesn’t know about credentials mode — when you use
credentials: 'include'in fetch, the server must returnAccess-Control-Allow-Credentials: trueand cannot use the wildcard*as origin. AI almost never understands this dependency. - People prompt-tweak instead of fixing structure — when CORS doesn’t work, the natural reflex is “fix this.” But each successive prompt without understanding the problem generates more code that masks rather than solves the issue. That’s the cascade I described above.
The best strategy: don’t ask AI to fix CORS. Understand the problem yourself, write the configuration manually (or use the code from this article), and tell AI to stay away from configuration files.
Production-Ready CORS Checklist
Before you deploy your application, go through these points. Each one is a potential source of CORS errors in production. If one is unmet, your app may break in ways that are hard to diagnose.
- Specific allowed origins. Never use
*in production. List every domain your frontend can send requests from. - Handle OPTIONS preflight explicitly. Every endpoint must respond to preflight with a 204 status and proper CORS headers. Without this, POST/PUT/DELETE won’t go through.
- Set Allow-Methods and Allow-Headers. The browser checks whether the method and headers you’re using are allowed. If you don’t list them, the request will be blocked.
- Handle credentials correctly. If you use cookies or tokens, you must set
Access-Control-Allow-Credentials: trueand you cannot use a wildcard origin. - Centralize CORS in one helper file. One change, one place. Don’t scatter configuration across files.
- Test with deployed frontend, not just localhost. CORS behaves differently on localhost. Test from the real domain after every deployment.
- 5xx errors still return CORS headers. If your error handler doesn’t add CORS headers, every server error will look like a CORS error in the browser.
- No URL mismatches. Check trailing slashes, www vs. non-www, http vs. https. Each mismatch is a different origin.
- Structured logging for CORS failures. Log rejected requests with origin, method, and headers. Without this, debugging in production is guesswork.
- Self-test after every deploy. A simple script that sends a cross-origin request to your API and verifies the response contains proper CORS headers.
Frequently Asked Questions
Why does my app work on localhost but get CORS errors in production?
On localhost, your frontend and backend usually run at the same address (e.g., http://localhost:3000), so the browser treats this as same-origin and doesn't apply CORS rules. After deployment, frontend and backend are on different domains — that's cross-origin and the browser starts checking for CORS headers. Additionally, many frameworks use a dev proxy that masks cross-origin — that proxy doesn't exist in production.
Should I use Access-Control-Allow-Origin: * ?
Only for fully public APIs that don't require authentication — e.g., public weather data or exchange rates. In every other case, use a list of specific domains. Remember: the wildcard * and Access-Control-Allow-Credentials: true cannot coexist. The browser will reject the response. If your application uses cookies, JWT tokens, or any form of session, the wildcard is not an option.
Is CORS a frontend or backend problem?
CORS is a backend configuration triggered by the frontend. The error appears in the browser (frontend), but the fix is on the server (backend). The server must return proper Access-Control-Allow-* headers for the browser to allow the request. You cannot "fix CORS" with frontend code changes alone — the only thing you can do on the client side is hide the problem (e.g., mode: 'no-cors'), which is worse.
Why does Postman work but my browser doesn't?
Postman is not a browser. Browsers enforce CORS — API clients don't. Postman, curl, Insomnia, server-to-server requests — none of them send preflight requests or check Access-Control-Allow-Origin headers. It's a pure browser mechanism. If your API works in Postman but not in the browser, it's 100% a CORS configuration issue on the server — not the API itself.
My AI fixed CORS but now other things are broken
This is the CORS cascade. AI doesn't understand your project's context. When you ask it to fix CORS, it often modifies files that shouldn't be touched — changes imports, modifies response shapes, rewrites middleware. Each successive fix makes things worse. The solution: roll back to the state before AI's "fix" (use git), understand the CORS problem using this article, and write the configuration manually. Don't let AI edit CORS configuration files.
CORS fixed but something else is broken?
CORS errors are often just the tip of the iceberg. If your AI app has issues after deployment, let's talk — we'll help you get from prototype to production.
Book a free call →