Fix CORS "Response to preflight request doesn't pass access control check" in dev
The problem
The browser console, every request:
Access to fetch at 'http://localhost:8000/api/items' from origin 'http://localhost:5173'
has been blocked by CORS policy: Response to preflight request doesn't pass access
control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
The OPTIONS preflight was rejected before my route handler ever ran.
What didn't work
- Adding
Access-Control-Allow-Originheaders inside the route handler — the preflight OPTIONS is answered (or rejected) by the framework/routing layer first and never reaches your handler. fetch(url, { mode: 'no-cors' })— the request "succeeds" and the response is an opaque empty object; you traded an error for silent data loss.- Verifying with curl — curl doesn't send preflights, so a passing curl test proves nothing about what the browser will do.
The fix
In development, make the requests same-origin with a proxy and CORS disappears entirely:
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true, // rewrites Host so the backend's own allowlist matches
},
},
},
});
# FastAPI — for genuinely cross-origin production traffic, use the middleware
# (it answers OPTIONS preflights for you):
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_methods=["GET", "POST", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
Why it works
Same-origin via the dev proxy means the browser never sends a preflight at all, and where cross-origin is unavoidable the CORS middleware answers OPTIONS with the exact allow-origin/method/header trio the browser checks before forwarding any real request.