跪拜 Guibai
← Back to the summary

Eight CORS Errors You'll Actually Hit — and One Chrome Just Invented

CORS errors are probably one of the hardest errors to read in front-end development: a block of English, three layers of nested quotes, and at first glance, it's impossible to tell who blocked whom. I've compiled the CORS errors I've stepped on in projects over the past few years, a total of 8. For each one, I provide the original error text from the field, the cause, and the solution. At the end, there's a quick-reference chart you can save directly—next time you encounter one, match it up in 10 seconds.

One heads-up: the 8th error didn't exist last year. It's the Private Network Access restriction that Chrome is rolling out gradually. Recently, most of the "my local environment ran fine for a year and suddenly throws a cross-origin error" help requests in the community are caused by this.

Let's review the CORS mechanism in 30 seconds

Browsers have a same-origin policy: pages from different origins (where any of the protocol, domain, or port differ) are not allowed to read each other's responses by default. CORS is a way to open a gap in this restriction—the server declares in the response header "which origin is allowed to read me", and the browser hands the data to JS only after the check passes.

So remember the first conclusion: CORS errors are almost always a problem with the server's response headers, not your front-end code. Your request was likely sent out, and the server returned a normal response, but the response header just lacked that "allow" statement.

Requests are also divided into two types:

Errors also fall into two categories: simple requests check the response header, preflighted requests check the OPTIONS response. The 8 errors below are matched accordingly.

Error 1: No Access-Control-Allow-Origin header (most common)

Original error text:

Access to fetch at 'https://api.example.com/data' from origin
'https://app.example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

The meaning is straightforward: the response header simply lacks Access-Control-Allow-Origin. Solutions fall into three tiers:

1) If you can modify the server, add the header directly. Express example:

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');
  next();
});

2) For local development where you can't change the backend, use a build tool proxy. Vite example:

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': { target: 'https://api.example.com', changeOrigin: true },
    },
  },
});

After configuration, the front end directly requests /api, and the dev server forwards it to the backend for you. The browser sees a same-origin request, and the cross-origin problem disappears at the root—this is also the most recommended method for local development.

3) In a production environment where the server is not under your control, you can only add a forwarding layer on your own server, preventing the browser from directly touching the other party's interface.

Error 2: Wildcard * cannot be used when carrying cookies

Original error text:

The value of the 'Access-Control-Allow-Origin' header in the response
must not be the wildcard '*' when the request's credentials mode is 'include'.

After the front end enables credentials: 'include' (to carry cookies), the server cannot lazily use Access-Control-Allow-Origin: *. It must echo the specific origin and also include Access-Control-Allow-Credentials: true. The correct approach is for the server to dynamically check a whitelist:

const allowList = new Set(['https://app.example.com']);
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowList.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }
  next();
});

Note: In this scenario, Access-Control-Allow-Headers and Access-Control-Allow-Methods also do not accept *; specific values must be written.

Error 3: Allow-Origin appears multiple times

Original error text:

The 'Access-Control-Allow-Origin' header contains multiple values.

This header can only appear once and can only have one value in the response. The typical cause is configuration at two layers: nginx added it once with add_header, and the application code set it again with setHeader. The browser receives two values and rejects it directly.

Solution: Check the entire chain across the three layers of nginx, gateway, and application framework, and keep it in only one place. When checking, open the DevTools Network panel to view the raw response headers; duplicates will clearly appear as two lines.

Error 4: Preflight does not allow the request method

Original error text:

Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response.

As mentioned earlier, DELETE is a complex request, so the browser first sends an OPTIONS request to ask for directions. The server's preflight response's Access-Control-Allow-Methods did not list DELETE, so the request was killed at the inquiry stage, and the real request was never sent.

The solution is to complete the list of methods used. Express example:

if (req.method === 'OPTIONS') {
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,X-Token');
  res.setHeader('Access-Control-Max-Age', '86400');
  return res.status(204).end();
}

Error 5: Preflight does not allow custom request headers

Original error text:

Request header field x-token is not allowed by Access-Control-Allow-Headers in preflight response.

Every non-simple header you add to a request—Authorization, custom X-Token—must be listed one by one in the server's Access-Control-Allow-Headers; not a single one can be missing.

An easily overlooked related issue: many HTTP libraries default to setting Content-Type to application/json, which itself upgrades the request to a complex request and triggers a preflight. If your interface actually only accepts form data, changing Content-Type back to application/x-www-form-urlencoded can save the entire round of preflight.

Error 6: Configuration was clearly changed, but the browser still reports the old error

There is no new error text for this; the symptom is "Errors 4 and 5 were fixed, but the browser still reports the exact same error."

Reason: Preflight results can be cached, and Access-Control-Max-Age is the cache duration. If it was previously set loosely to 86400 (one day), then for a day after you change the configuration, the browser will directly use the old preflight result without asking again.

Solution: During debugging, set Access-Control-Max-Age to 0, or check "Disable cache" in DevTools, then test again.

Error 7: The redirect target has no CORS header

Original error text (note that two URLs appear inside):

Access to fetch at 'https://b.example.com/data' (redirected from
'https://a.example.com/api') from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header
is present on the requested resource.

CORS checks the final response. If the interface 302 redirects to another domain—common cases include redirecting to a login center, a CDN, or a gateway—then the final domain's response must also carry CORS headers. The front end has no solution for this error; it can only be handled by the server: either remove the redirect, or configure headers on the redirect target as well.

Error 8: Chrome Private Network Access (a new pitfall that didn't exist last year)

The original error text looks very similar to traditional cross-origin errors, but it includes an extra key piece of information:

The request client is not a secure context and the resource is in
more-private address space `local`

Or during the preflight phase:

No 'Access-Control-Allow-Private-Network' header is present on the
requested resource.

Background: Chrome is rolling out Private Network Access restrictions in phases—a public web page that wants to access "more private" network resources like localhost or intranet IPs must first pass a preflight, and the server must additionally return a header:

Access-Control-Allow-Private-Network: true

At the same time, the page itself must be in a secure context (https or localhost). This is the reason for the recent "my local environment ran fine for a year and suddenly started throwing cross-origin errors": your code didn't change; the browser's rules are tightening.

Responses vary by scenario:

This restriction is still being rolled out gradually; it's recommended to add it to your troubleshooting checklist now, otherwise one day someone on the team will always come and ask you "why did everything suddenly break?".

CORS Error Quick Reference Chart

Error Keyword Cause Solution
No 'Access-Control-Allow-Origin' header Server did not return this header Server adds header / Build tool proxy / Server-side forwarding
must not be the wildcard '*' Wildcard used while carrying cookies Dynamically echo whitelisted origin + Allow-Credentials
contains multiple values Same header configured at two layers Check nginx/gateway/app, keep only one
not allowed by Access-Control-Allow-Methods Preflight did not allow this method Complete the methods in Allow-Methods
not allowed by Access-Control-Allow-Headers Custom header not listed Add them one by one to Allow-Headers
Configuration changed but old error persists Preflight result cached by max-age Set max-age to 0 or disable cache and test again
redirected from ... Redirect target lacks CORS header Server removes redirect or adds header to target
more-private address space Chrome PNA private network restriction Add Allow-Private-Network: true + secure context

Three Common Misconceptions

Misconception 1: mode: 'no-cors' can solve cross-origin issues. It cannot. It just makes the request not throw an error, but what's returned is an opaque response—status code unreadable, response body unreadable, equivalent to sending nothing at all. Its only legitimate use case is for scenarios like analytics reporting where "you don't need to read the response."

Misconception 2: Just install an Allow CORS browser extension. The extension only injects headers into the response for your local browser; users' browsers in production have no such extension. It's only suitable for local debugging emergencies. Treating it as a solution guarantees a failure upon deployment.

Misconception 3: Bypass it with JSONP. JSONP only supports GET, requires specific backend cooperation, and carries injection risks. Almost no new projects use it now. In this day and age, just follow standard CORS properly.

Final Words

CORS errors look intimidating, but essentially there are only three categories: the server didn't provide the header, the header value is wrong, or the browser added a new rule (PNA is the third category). Next time you see an error, first copy the error text and match it against the quick reference chart above; don't just start randomly adding headers and hoping for the best—randomly adding headers will only turn Error 1 into Error 3.

Which CORS error have you stepped on? Or have you recently encountered a "sudden cross-origin" paranormal event? Let's chat in the comments. If the quick reference chart is useful, please bookmark it; I'll continue to add new errors as I encounter them.

I'm a front-end developer who organizes original error texts into quick reference charts. Follow me, and you won't panic next time you see an error.