TypeError: Failed to fetch is the browser saying that a fetch() call never completed. Not that it failed - that it never got far enough to have a result at all.

The distinction matters more than anything else on this page, because it rules out most of what people first suspect. A 404 does not produce this. Neither does a 500. Those are completed requests: the server answered, fetch() resolves, and response.ok is false. Failed to fetch means there is no response object to inspect, which is exactly why the message tells you so little.

Chrome, Firefox and Safari each word it differently - Failed to fetch, NetworkError when attempting to fetch resource, Load failed - and mean the same thing.

The causes, in the order they actually occur

CORS. The most common by a distance. The request went out, the server answered, and the browser refused to hand the response to your code because the headers did not permit it. The console prints a separate message about the origin policy, and the fetch rejects with the generic error. If you read only the rejection you learn nothing; the line above it is the actual answer.

The connection never happened. Offline, DNS failure, the host unreachable, the server not listening. This is the same territory as the ERR_CONNECTION family, and the network panel names it.

Something blocked it. An ad blocker, a privacy extension, a corporate proxy. Requests to anything that looks like analytics or tracking are refused before they leave the browser, and the network panel marks them blocked. This one is invisible on your own machine if you are the only person without that extension.

Mixed content. An https page asking for an http URL. The browser blocks it outright.

The request was cancelled. The page navigated away, a component unmounted, an AbortController fired, the tab was closed mid-request. Ordinary in a single-page application, and it shows up in error tracking as a flood of failures that never happened to anybody.

A bad URL. A typo, a relative path resolving somewhere unintended, an environment variable that was empty at build time so the request went to undefined/api/thing.

Failed to fetch dynamically imported module

A specific version worth its own paragraph, because the cause is not in your code and the fix is not either.

An application built with code splitting loads chunks with hashed filenames. You deploy, the hashes change, the old files stop existing. Anybody with the page already open - a tab left open overnight, a phone that never reloaded - asks for a filename that is now a 404, and gets this error the moment they navigate to a route whose chunk has not loaded yet.

Two fixes, and they work together. Keep the previous build’s assets around for a while rather than replacing them, so open tabs keep working. And catch the failure at the router: if a dynamic import rejects, offer a reload, because a reload genuinely fixes it.

How to find out which one you have

The console will not tell you. The network panel will, and it takes about ten seconds.

Open it, reproduce the failure, and find the request in red. Then read three columns:

  • Status. Empty or (failed) means the request never completed. (blocked:...) names the thing that blocked it.
  • The host. Your own domain, or somebody else’s. A failure against a third party is a different problem from a failure against your own API.
  • The console message directly above the rejection. If it mentions an origin or a preflight, you have a CORS problem and everything else on this page is a distraction.

If the request does not appear in the network panel at all, it never left the browser: mixed content, an extension, or a URL that was never valid.

Session Replay

Free Chrome extension. One click on the page that is misbehaving captures the screenshot, the console and the network log, and hands you a link to paste into the ticket.

Get the extension

Why it is the worst error to receive second-hand

Everything above assumes you can reproduce it. Most of the time you cannot, because the causes that produce this error are the ones that live on somebody else’s machine: their extension, their network, their corporate proxy, their stale tab.

So it arrives as “the page just says something went wrong”, and the console message they might quote back to you - Failed to fetch - is the one message on the page that carries no information. The evidence that would settle it is one row in a network panel nobody is looking at.

That is the argument for capturing the network log at the moment it happens rather than asking questions afterwards. A HAR file is the manual way to do it. Either way, what settles this class of bug is the row, not the sentence.

The short version

  • It means the request never completed. No response, no status code, nothing to inspect.
  • A 404 or a 500 does not cause it - those are successful round trips with unhappy answers.
  • Check the console line above the rejection first. If it mentions CORS, that is your answer.
  • Then check whether the request even appears in the network panel. If it does not, something in the browser stopped it before it left.
  • After a deploy, “failed to fetch dynamically imported module” means an old tab is asking for a file your last build deleted.