Access to fetch at 'https://api.example.com' from origin 'https://myapp.com' has been blocked by CORS policy
appears when:After deploying to a real domain when the fetch worked locally on http://localhost:3000
CORS error in production only
The server is responding — the browser refuses to hand the data to your JavaScript because Access-Control-Allow-Origin does not match the live domain.
http://localhost:3000. For Supabase/Clerk, add the production origin in the dashboard. For a first-party Next.js API route, return Access-Control-Allow-Origin matching the request origin plus a handler for OPTIONS preflights. Never use * with credentials: "include".Quick fix for CORS error in production
01// middleware.ts — add CORS headers for /api/* routes02import { NextResponse } from "next/server";03import type { NextRequest } from "next/server";04 05const ALLOWED = ["https://myapp.com", "https://www.myapp.com"];06 07export function middleware(req: NextRequest) {08 const origin = req.headers.get("origin") ?? "";09 const allowed = ALLOWED.includes(origin) ? origin : ALLOWED[0];10 11 if (req.method === "OPTIONS") {12 return new NextResponse(null, {13 status: 204,14 headers: {15 "access-control-allow-origin": allowed,16 "access-control-allow-methods": "GET, POST, OPTIONS",17 "access-control-allow-headers": "content-type, authorization",18 "access-control-allow-credentials": "true",19 "access-control-max-age": "86400",20 "vary": "origin",21 },22 });23 }24 25 const res = NextResponse.next();26 res.headers.set("access-control-allow-origin", allowed);27 res.headers.set("access-control-allow-credentials", "true");28 res.headers.set("vary", "origin");29 return res;30}31 32export const config = { matcher: "/api/:path*" };Deeper fixes when the quick fix fails
01 · Proxy client fetches through a Next.js API route
CORS only fires for browser requests. Move the fetch to a server route. The browser hits your own origin, then your server fetches Supabase/Stripe with no CORS check. Also hides the API key.
01// app/api/supabase/search/route.ts02export async function POST(req: Request) {03 const { query } = await req.json();04 const r = await fetch(`${process.env.SUPABASE_URL}/rest/v1/tasks?name=ilike.${query}`, {05 headers: {06 apikey: process.env.SUPABASE_SERVICE_ROLE_KEY!,07 authorization: `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`,08 },09 });10 return Response.json(await r.json());11}02 · Return the exact origin when using credentials
Browsers reject Access-Control-Allow-Origin: * paired with credentials: "include". Echo the request origin (from an allow-list) and add Vary: Originso CDNs do not cache one origin’s response for another.
Why AI-built apps hit CORS error in production
Every third-party API keeps a list of origins that are allowed to call it from a browser. Supabase calls it “Allowed Origins.” Clerk uses instance settings. AI builders add http://localhost:3000 during preview. The production domain is never added because the builder does not know what you will deploy to. The first real user hits the live page, the app tries to fetch Supabase, and the browser blocks.
The second cause is first-party API routes that return data to a client on a different subdomain. A Next.js app at app.myapp.com and an API at api.myapp.com are different origins. The API route must return Access-Control-Allow-Origin: https://app.myapp.com. AI-generated API routes rarely set these headers because they are unnecessary in the monolithic preview.
The third pattern is the preflight failure. Browsers send an OPTIONS request before any fetch with custom headers or credentials: "include". The AI-generated handler responds to GET/POST but not OPTIONS. The preflight 404s. The browser surfaces a generic CORS error. Fix by handling OPTIONS or using Next.js middleware.
CORS error in production by AI builder
How often each AI builder ships this error and the pattern that produces it.
| Builder | Frequency | Pattern |
|---|---|---|
| Lovable | Every Supabase scaffold | Adds localhost only — prod origin missing |
| Bolt.new | Common | No OPTIONS handler on API routes |
| v0 | Common | Uses wildcard * with credentials include |
| Cursor | Sometimes | Splits app and API into different subdomains without CORS config |
| Replit Agent | Rare | Skips Vary: Origin — CDN serves wrong origin header |
Related errors we fix
Stop CORS error in production recurring in AI-built apps
- →Whitelist production origins in every Supabase/Clerk project at provisioning time, not deploy time.
- →Prefer server proxies for third-party APIs — CORS becomes a non-issue.
- →Always include `Vary: Origin` so CDNs do not cross-contaminate responses.
- →Write a Playwright test that hits the endpoint from the production URL in CI.
- →Document the exact origin list in README and lock it behind a typed config object.
Still stuck with CORS error in production?
CORS error in production questions
What does 'Access-Control-Allow-Origin' missing actually mean?+
Why does CORS fail only in production?+
Can I fix CORS with a server proxy?+
What is a preflight request?+
How long does a CORS fix take?+
Ship the fix. Keep the fix.
Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.
Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.