The Error

Your frontend calls an API and the browser console shows:

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

The same request works in Postman or curl. That's the clue: CORS is enforced by browsers, not servers. The server must tell the browser that your site is allowed to read its responses. You can't fix CORS from frontend code alone.


🎯 Quick Fix

On the API server, return the right headers for your frontend's origin:

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

And make sure OPTIONS (preflight) requests return those headers with a 2xx status. If you don't control the API, call it from your own backend instead of the browser.


What CORS Actually Is

An origin is scheme + domain + port. http://localhost:3000 and http://localhost:8000 are different origins. Browsers block a page from reading responses from another origin unless that server opts in. CORS (Cross-Origin Resource Sharing) is the opt-in.

sequenceDiagram participant P as Page (localhost:3000) participant B as Browser participant A as API (api.example.com) P->>B: fetch(POST /data, JSON, Authorization) B->>A: OPTIONS /data (preflight)
Origin: http://localhost:3000 A-->>B: 204 + Access-Control-Allow-Origin,
-Methods, -Headers alt Allowed B->>A: POST /data A-->>B: 200 + Access-Control-Allow-Origin B-->>P: ✅ response else Missing / wrong headers B-->>P: ❌ CORS error (response hidden) end

Preflight: for "non-simple" requests (JSON bodies, Authorization headers, PUT/DELETE), the browser first sends an OPTIONS request to ask permission.


Fixes by Server

Express (Node.js)

import cors from "cors";

app.use(cors({
  origin: ["http://localhost:3000", "https://myapp.com"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true, // only if you send cookies
}));

Next.js Route Handler

// app/api/data/route.ts
const ALLOWED = "https://myapp.com";

const corsHeaders = {
  "Access-Control-Allow-Origin": ALLOWED,
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
};

export async function OPTIONS() {
  return new Response(null, { status: 204, headers: corsHeaders });
}

export async function POST(req: Request) {
  const body = await req.json();
  return Response.json({ ok: true, body }, { headers: corsHeaders });
}

If your frontend and API are in the same Next.js app on the same domain, there's no CORS issue at all: call /api/data with a relative URL.

FastAPI (Python)

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_methods=["*"],
    allow_headers=["*"],
    allow_credentials=True,
)

Common Traps

Symptom Cause Fix
Works for GET, fails for POST/JSON Preflight OPTIONS not handled Return CORS headers on OPTIONS with 2xx
* not allowed with credentials Access-Control-Allow-Origin: * plus cookies Return the exact origin and Access-Control-Allow-Credentials: true
CORS error on a 500 / 401 Error responses skip CORS middleware Add CORS headers to error responses too
Works locally, fails in production Production origin not in the allow list Add https://yourdomain.com (exact, no trailing slash)
Redirect breaks the request API redirects http→https or adds a slash Call the final URL directly
Don't "fix" CORS with mode: 'no-cors'. It doesn't bypass anything: the browser gives you an empty, unreadable "opaque" response. And don't install browser extensions that disable CORS, since your users won't have them.

When You Don't Control the API

Third-party APIs often don't allow browser calls (and putting their API keys in the browser would expose them anyway). Call them from your server:

flowchart LR B["Browser"] -->|"same origin"| S["Your backend /
Next.js route handler"] S -->|"server-to-server
(no CORS)"| T["Third-party API"]

Server-to-server requests aren't subject to CORS, and your API key stays secret.


Debugging Checklist

  • Open DevTools → Network, find the OPTIONS request: does it return 2xx with CORS headers?
  • Is Access-Control-Allow-Origin your exact origin (scheme, domain, port)?
  • Using cookies? credentials: 'include' in fetch and Allow-Credentials: true with a non-wildcard origin
  • Custom headers listed in Access-Control-Allow-Headers?
  • Do error responses also include CORS headers?

Frequently Asked Questions

Why does my API work in Postman but not in the browser?

CORS is enforced by browsers to protect users. Postman, curl and servers don't apply it. The browser blocks your page from reading the response unless the API server returns headers allowing your origin.

Can I fix a CORS error from the frontend?

No. The server must send the Access-Control-Allow-Origin header. If you don't control the server, route the request through your own backend or a server-side function, which isn't subject to CORS.

What is a CORS preflight request?

For requests with JSON bodies, custom headers or methods like PUT and DELETE, the browser first sends an OPTIONS request asking the server whether the real request is allowed. The server must respond with the appropriate CORS headers and a success status.


Conclusion

A CORS error means the API server didn't tell the browser your site may read its responses. Fix it on the server: return the exact allowed origin, handle OPTIONS preflight requests, include credentials headers when using cookies, and add CORS headers to error responses. If you don't control the API, call it from your own backend.


Sources

Last verified: 2 November 2026.


Related reads: WebSocket vs SSE · JWT vs Session Auth · How to Use Environment Variables in Next.js

I write practical guides for developers. Follow on LinkedIn or X, or invite me to speak.