Debugging network problems

Read the Network panel properly, decode a CORS failure, capture a HAR file, and tell a timeout from an abort from a network failure.

The Network panel

ToolWhat it answers
Filter box, Fetch/XHRwhich requests were made by script, without images and fonts
status:0 or method:POST filtersfind the failures or the writes quickly
Request headers viewwas the header actually sent, with the value you intended
Timing tabwas the time spent queued, connecting, waiting for the server, or downloading
Response tab on a failing callthe server's error body, which the console never shows
Preserve logkeep the requests from before a navigation or a reload
Throttling presetsreproduce a slow connection and expose races you cannot see locally
  • A request that never appears in the panel never left the browser: it was blocked, deduplicated by the cache, or the code threw before calling fetch.
  • The queueing row in the timing tab is the browser's own connection limit. If most of the bar is queueing, your problem is concurrency, not the server.
  • Searching all requests for a response header or a body string finds the call you forgot you made, which is often the actual bug.
  • Copy as fetch from the context menu reproduces the request exactly, including headers, and is far faster than rebuilding it by hand.

Reading a CORS failure correctly

The console message names the cause once you read it as three parts: the request, the response headers, and the missing header. Everything else in the message is boilerplate.

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

read as:
  request origin ... https://app.example.com
  resource ......... https://api.example.com/me
  missing ......... Access-Control-Allow-Origin

variants and what they mean
  "Response to preflight request doesn't pass access control check"
      -> the OPTIONS request failed; check Allow-Methods and Allow-Headers
  "The value of the 'Access-Control-Allow-Origin' header ... must not be the wildcard '*'
      when the request's credentials mode is 'include'"
      -> echo the exact origin instead of *
# reproduce without CORS: curl is not a browser and ignores the policy
curl -i -X OPTIONS 'https://api.example.com/me' \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: PUT' \
  -H 'Access-Control-Request-Headers: content-type'

# if the headers are missing here, the problem is on the server, not in your code
curl -i 'https://api.example.com/me' -H 'Origin: https://app.example.com'
⚠️
A CORS error is not evidence that the request failed. For a simple POST the server already processed it, and the browser only withheld the response. Check the server logs before retrying, or you may create the record twice.

HAR files, and naming the failure

SymptomWhat it isWhat to do
AbortErroryour code or a component aborted the signalexpected if you aborted on purpose; log the reason otherwise
TypeError: Failed to fetchthe request never completed: offline, DNS, TLS or a blocked requestcheck the panel and the network, not the server
TimeoutErrorAbortSignal.timeout expiredconsider a longer budget or a server-side change, and retry with backoff
Status 0no HTTP status was receivedtreat as no response, never as a server error
Status 504 or 502a proxy could not reach the originlook at the gateway log, it knows which upstream failed
Status 200 with the wrong content typea proxy or a login page answered instead of the APIverify the URL and the authentication state
// name the failure at the source, so a report says which case it was
async function labelledFetch(url, init) {
  try {
    const res = await fetch(url, init);
    if (!res.ok) throw new Error('HTTP ' + res.status + ' from ' + url);
    return res;
  } catch (err) {
    if (err.name === 'AbortError') throw new Error('aborted: ' + url);
    if (err.name === 'TimeoutError') throw new Error('timeout: ' + url);
    if (err.name === 'TypeError') throw new Error('network or CORS: ' + url);
    throw err;
  }
}

// a HAR export captures every request, response and timing in one file
// DevTools -> Network -> right-click -> Save all as HAR with content
// it is the artifact to attach to a bug report, and it replays in other tools

A HAR file contains cookies and authorisation headers, so it is a credential-bearing artifact. Redact it before it leaves the team, and never attach one to a public issue.

FAQ

The request works in curl but fails in the browser. Why?
curl does not enforce the same-origin policy and does not send an Origin header, so it never triggers CORS. If the browser call fails and curl succeeds, the server is missing the CORS response headers. It is not a client bug.
How do I tell a timeout from an abort?
Both surface as an abort, but the error names differ: AbortSignal.timeout produces a TimeoutError, while controller.abort() produces an AbortError. Check the name in the catch block and label the error before it is reported.

Same-origin policy, CORS and credentials Streaming and long-lived connections

Last refreshed 2026-09-18.