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, AuthorizationAnd 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.
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 |
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:
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-Originyour exact origin (scheme, domain, port)? - Using cookies?
credentials: 'include'in fetch andAllow-Credentials: truewith 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
- MDN: Cross-Origin Resource Sharing (CORS)
- MDN: CORS errors reference
- Express: cors middleware
- FastAPI: CORS
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.