A browser security feature blocks a web page from making a request to a different origin unless that origin explicitly allows it.
What "CORS error blocked by policy" Means
The browser is enforcing the Same-Origin Policy. Your frontend JavaScript tried to fetch a resource from a different domain, port, or protocol, and the server didn't include the required Access-Control-Allow-Origin header in its response. The request was sent, but the browser refused to expose the response to your code.
This is not a server-side failure — the server likely processed the request fine. The browser is the gatekeeper, and it's blocking you.
Why It Happens
Three real causes cover 95% of cases:
- Missing header on the server. The backend doesn't send
Access-Control-Allow-Origin: *(or your specific origin) in the response. This is the most common cause. - Preflight failure. For non-simple requests (e.g.,
Content-Type: application/json, custom headers, orDELETE/PUTmethods), the browser sends anOPTIONSpreflight request first. If the server doesn't respond with the correctAccess-Control-Allow-MethodsandAccess-Control-Allow-Headers, the actual request never fires. - Mismatched origin. You're testing from
http://localhost:3000but the server only allowshttp://localhost:5173— or you're usingfile://protocol, which sendsOrigin: null.
Example Code That Triggers It
Here's a minimal Next.js API route that will produce the error when called from a frontend on a different port:
// app/api/data/route.ts (Next.js App Router)
export async function GET() {
const data = { message: "hello" };
return Response.json(data);
}
And the frontend:
// app/page.tsx
const res = await fetch("http://localhost:3000/api/data");
const json = await res.json();
If the frontend runs on http://localhost:3001 (or any other origin), the browser blocks the response. The fetch will throw TypeError: Failed to fetch, and the console shows "CORS error blocked by policy".
How to Fix It
Add the CORS headers to the API response:
// app/api/data/route.ts
export async function GET() {
const data = { message: "hello" };
return new Response(JSON.stringify(data), {
headers: {
"Access-Control-Allow-Origin": "*", // or your specific origin
"Content-Type": "application/json",
},
});
}
This works because the browser checks the Access-Control-Allow-Origin header against the requesting origin. * allows any origin — fine for public APIs, but for anything with credentials, use your exact origin instead (e.g., http://localhost:3001). For non-GET requests, you also need to handle the OPTIONS preflight:
export async function OPTIONS() {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
Common Mistakes That Cause This
-
Using
fetchwithcredentials: "include"while the server sendsAccess-Control-Allow-Origin: *. Browsers reject wildcard origins when credentials are involved. You must echo the specific origin back, not use*. -
Setting CORS headers only on the error response path. If your server has middleware that sets headers on success but not on error responses, the browser still blocks it. Set headers globally — in Next.js, that's
next.config.jsor a middleware file — not per-route.
When Should You Worry About This?
If you're seeing this in development, don't panic — it's almost always a config issue, not a security breach. Worry when it appears in production: it means your API is misconfigured and real users can't access it. Also worry if your API is public and you're using Access-Control-Allow-Origin: * with sensitive data — anyone can read it from any site.
In production, the fix is the same as development: configure CORS at the server or gateway level (nginx, Vercel, etc.), not in individual route handlers.
Next time this error appears, check the server response headers in the Network tab first — if Access-Control-Allow-Origin is missing, that's your problem. If it's present, check the preflight OPTIONS request and its headers.