Preflight OPTIONS Requests
When browsers send CORS OPTIONS preflights, which headers to return, caching Max-Age, and SPA debug tips.
- browser
- cors
- preflight
- options
- fetch
A CORS preflight is an automatic OPTIONS request the browser sends before certain cross-origin calls to ask the server, “are you OK with this method and these headers?” Your React code never writes it; the browser does. Failed preflights surface as opaque CORS errors even though the “real” POST never runs.
Docs: MDN Preflight, Fetch CORS protocol, CORS explained.
When preflight happens
Rough triggers for a non-simple request:
- Methods other than
GET/HEAD/POST(e.g.PUT,PATCH,DELETE) Content-Typeother than simple values (notablyapplication/json)- Custom headers (
Authorization,X-Request-Id, …) - Other non-safelisted bits (e.g. some
ReadableStreambodies)
// Typically triggers preflight cross-origin
await fetch('https://api.example.com/v1/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ name: 'x' }),
});
What the browser sends
OPTIONS /v1/items HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization
What the server must answer
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 7200
Vary: Origin
With cookies / credentialed mode:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Never combine credentials with Allow-Origin: *.
Max-Age
Access-Control-Max-Age caches the preflight result (browser-dependent caps). Higher values reduce OPTIONS chatter in apps that fire many JSON APIs. During CORS debugging, temporary low max-age avoids “I fixed the server but browser still fails” confusion — hard-refresh / clear cache.
Frontend vs backend ownership
| Layer | Responsibility |
|---|---|
| Browser | Sends OPTIONS when required |
| API / gateway | Answers allow-origin/methods/headers |
| SPA | Avoid unnecessary custom headers; fix only if you control API |
| Dev proxy | Hides cross-origin in local dev only |
Adding random headers “for debugging” (X-Debug: 1) forces preflights. Batch intentional headers.
Debugging checklist
- Network panel: is OPTIONS red? Read its status and ACAO headers.
- OPTIONS 404/405 → gateway doesn’t handle OPTIONS for that route.
- OPTIONS 204 but POST still fails → real response missing ACAO.
- Works in Postman → expected; Postman is not a browser.
Interview out-loud
“Preflight is an OPTIONS request for non-simple CORS calls like JSON POST with Authorization. The server must allow origin, method, and headers; Max-Age caches the result. Frontend rarely fixes this without API changes.”
Gateway config sketch
Ensure API gateways answer OPTIONS on the same path as POST, with 204/200 and no auth challenge that blocks preflight (some WAFs require exceptions for OPTIONS). Log preflight failures separately — they never hit your controller logic, so app logs look “empty” while browsers fail.
Related
Further depth
Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.
Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.
Further reading
Related guides
- CORS Explained for FrontendWhy the browser blocks cross-origin responses, what Access-Control-* headers mean, preflight, credentials, and how to fix real frontend errors.
- Same-Origin PolicyOrigin tuple (scheme/host/port), what SOP blocks, and how CORS, postMessage, and cookies relate.
- BFCache Back Forward CacheHow the back/forward cache freezes pages for instant history nav, what blocks it, and how to restore state safely.
- Browser DevTools Network PanelRead waterfalls, timing phases, headers, throttling, and initiator chains in the Network panel like a production debugger.
- Browser DevTools Performance PanelRecord main-thread timelines: long tasks, style/layout/paint, frames, and how to turn flame charts into INP fixes.