Partly fixable yourself. Often one missing header or one missing allowed origin.
What you see. The app works in preview, but on the live domain requests fail and the browser console shows a CORS error.
Why it happens
CORS stands for cross-origin resource sharing. It is a browser rule. A page on one domain may only read a response from another domain if that server says so, with a header called Access-Control-Allow-Origin. Your preview URL was allowed, your live domain is not. The browser blocks the response and only shows the reason in the console, so your app just sees a failed request.
With AI-built apps the server piece is usually a Supabase Edge Function, a /api route on Vercel, or a Netlify function. The function either never answers the preflight OPTIONS request, or returns the header only for the old origin. On Netlify, rules in the _headers file apply to static files only, not to functions, so a header added there does nothing for your API. The fix is always on the server, never in the browser.
Fix it yourself
- Open the live site, press F12 and read the Console. The line starting with Reason tells you which header is missing or which origin does not match.
- In the Network tab click the red request. Its URL shows what is being called: a Supabase Edge Function, a Vercel
/apiroute, or a Netlify function. - Supabase Edge Function: it must answer
OPTIONSand returncorsHeaderson every response, including errors. Ask your builder to use thewithSupabasewrapper or importcorsHeadersfrom@supabase/supabase-js. - Vercel or Netlify function: the function code itself must set
Access-Control-Allow-Originto your live domain. A headers block invercel.jsonor_headersonly covers static files. - Add the live domain exactly as the browser shows it, with https and no trailing slash. Redeploy, then hard refresh the live site.
- Never fix this by calling Supabase with the secret key from the browser or by routing through a public CORS proxy. Both leak data.
When to get an engineer
Stop and get an engineer when the failing calls are spread across several functions, or when the error moves to a different header each time the AI touches it. Tell them the exact console message, the failing URL, and your live domain. A single fix is €99–$990 and takes about two days, and it should include a check that every function returns the same headers on success and on error.
Top-rated companies for Supabase apps
Ranked by our score: published prices, scope of work, AI tools named, timing and a low-risk first step. How we score.
- 1Redlab10.0/10Best for: A full rescue by one team that keeps buildingFree first step · from $490
- 2Vibe Code Rescue (Lev Arakelyan)9.0/10Best for: A one-off check and a cleanupFrom $299
- 3LazyCodeLab9.0/10Best for: A one-off check and a cleanupFrom $399
Check these
No secret keys reach the browser
Open your live app, then DevTools, Sources, and search the loaded JavaScript for sb_secret_, sk_live_, sk-proj- and sk-ant-. Older Supabase keys are long tokens starting with eyJ: decode the middle part of each on your own machine, and if it contains "role":"service_role", your secret key is public. A publishable or anon key in the browser is expected.
Secrets live in the host's environment settings
Keys belong in environment variables on the server, not in the repository or in front-end code.
Input is validated on the server
Forms that only check input in the browser accept anything sent directly to the API.
Questions
Why does it work in the Lovable or Bolt preview?
The preview runs on a domain the function already allows, often with a wildcard. Your custom domain is a new origin the server has not been told about.
Is a CORS error a security problem?
The error itself is the browser protecting you. The danger is in quick fixes: allowing every origin on a function that uses a secret key, or moving the secret key into the browser.
Sources: MDN: CORS errors; Supabase: CORS for Edge Functions; Netlify: custom headers; Vercel: vercel.json headers.
