← Back to the library
FrontendApplied · 8 min

CORS fails only in production or on preflight

“Requests work locally and in curl, but the deployed app shows 'blocked by CORS policy', the OPTIONS request fails, and fetch rejects with a generic TypeError.”

THE PRINCIPLE

JavaScript only learns that the request failed; the reason is deliberately hidden. Debug from the console message and the network panel, never from the caught error.

FIRST MOVES

  1. Copy the console reason and inspect the OPTIONS request and response headers in the network panel.
  2. Preflights carry no cookies or Authorization: answer OPTIONS before auth middleware, with a 2xx status.
  3. Keep redirects away from preflights; an http→https, trailing-slash, or login redirect makes the preflight fail.
  4. With credentials, echo an allowlisted origin, set Allow-Credentials: true, and list methods and headers explicitly; '*' does not apply.
  5. When Allow-Origin depends on the request, send Vary: Origin so CDNs and browsers do not reuse another origin's response.
  6. Compare the response at the origin with the response through the CDN, proxy, or gateway; edge layers often strip headers or cache OPTIONS.

TOOLS: THEN → NOW

Access-Control-Allow-Origin: * everywhereExplicit origin allowlist for credentialed APIs; '*' remains fine for public, cookie-less resources
Dev-server proxy hiding CORS locallySame-origin routing or a staging environment with production edge config

PATTERN SNAPSHOT

const ALLOWED = new Set(["https://app.example.com"]);
// Runs before auth and redirects: preflights never carry credentials.
if (req.method === "OPTIONS") {
  const origin = req.headers.get("Origin") ?? "";
  const h = new Headers({ Vary: "Origin", "Access-Control-Max-Age": "600" });
  if (ALLOWED.has(origin)) {
    h.set("Access-Control-Allow-Origin", origin); // never "*" with credentials
    h.set("Access-Control-Allow-Credentials", "true");
    h.set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE");
    h.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
  }
  return new Response(null, { status: 204, headers: h });
}
// Actual responses also need Allow-Origin, Allow-Credentials, and Vary: Origin.

CLOSE THE AI. EXPLAIN THIS.

Why can a missing Vary: Origin header produce CORS failures that appear only for some users behind a CDN?

HOW IT WORKS UNDERNEATH

SOURCES

Guide reviewed