Problem
Browser apps often pay for an extra network request before the real API request. If the request uses a custom auth header, a non-simple content type, or a method such as PUT, PATCH, or DELETE, the browser sends an OPTIONS preflight first.
That preflight is correct browser behavior, but it can quietly dominate perceived latency. In one measurement, repeated preflight calls were taking more than 250 ms each before the real request even started.
Mechanism
A preflight asks the server whether a cross-origin request is allowed for a specific origin, method, and set of headers. If the server says yes, the browser sends the real request.
OPTIONS /sessions HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, x-access-tokenIf your application makes many authenticated calls during startup, the browser may be doing a matching preflight for each endpoint. The API looks slow even if the backend handler is fast.
Fix
Cache successful preflight responses with Access-Control-Max-Age. In Flask-CORS, this is a one-line change.
CORS(
app,
origins="*",
methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers="*",
max_age=86400,
)The browser can now reuse the preflight result for the same origin, endpoint, method, and headers instead of asking again on every call.
What changed in practice
| Case | Preflight behavior | Observed overhead |
|---|---|---|
| Before | Repeated OPTIONS calls | 250 ms plus per API call |
| After | Cached preflight result | Skipped on repeat calls |
Production lesson
If a browser app calls a public API with custom headers, inspect the network waterfall for OPTIONS. The fastest handler cannot help if every request is preceded by an avoidable round trip.