Skip to content

TypeError: Failed to fetch

fetch() rejects with this error when no HTTP response reaches your code. The request was blocked, or the connection failed. A 404 or 500 status alone never causes it.

Updated September 24, 2026 by the Squasher team

What the error means

fetch() resolves for every HTTP response, including 404 and 500. It rejects with a TypeError only when the browser gets no response it can give to your code.

The browser hides the reason from JavaScript on purpose, so a CORS block, a DNS failure, and a dropped connection all look the same in code. The real reason is in the DevTools Console and Network panel, for example net::ERR_CONNECTION_REFUSED or a CORS message.

The error message in each browser or library
WhereMessage
Chrome, EdgeTypeError: Failed to fetch
FirefoxTypeError: NetworkError when attempting to fetch resource.
SafariTypeError: Load failed

Common causes

  • CORS: the response has no Access-Control-Allow-Origin header for your origin, or the preflight OPTIONS request fails. Error pages from a proxy or gateway often leave these headers out.
  • The request sends credentials: "include", but the server answers with Access-Control-Allow-Origin: *. Credentialed requests need the exact origin and Access-Control-Allow-Credentials: true.
  • The network failed: the device is offline, DNS lookup failed, or the connection was refused, reset, or rejected for a bad TLS certificate.
  • Mixed content: an HTTPS page calls an http:// URL, and the browser blocks it.
  • A browser extension or content blocker stopped the request (net::ERR_BLOCKED_BY_CLIENT).
  • The user left or reloaded the page while the request was still running.

Reproduce it

In DevTools, open Network, right-click the request, and choose Block request URL. Or set throttling to Offline. Then run the request again.

TypeScript
try {  const response = await fetch("https://api.example.com/profile");  console.log(response.status);} catch (error) {  // Chrome: TypeError: Failed to fetch  // Safari: TypeError: Load failed  console.error(error);}

Fix it

  1. Tell network failures apart from HTTP errors

    Catch the rejection for failures without a response. Check response.ok for error statuses. They need different messages and different retry rules.

    TypeScript
    async function getProfile() {  let response: Response;  try {    response = await fetch("/api/profile");  } catch (error) {    // No response at all: offline, DNS, CORS, or blocked    return { ok: false, reason: "network", error } as const;  }  if (!response.ok) {    return { ok: false, reason: "http", status: response.status } as const;  }  return { ok: true, profile: await response.json() } as const;}
  2. Send the right CORS headers

    Fix CORS on the server, not in the browser. For a credentialed request, name the origin, allow credentials, and answer the preflight for the methods and headers you use.

    Response headers
    Access-Control-Allow-Origin: https://app.example.comAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Methods: GET, POSTAccess-Control-Allow-Headers: Content-Type, AuthorizationVary: Origin
  3. Retry only safe requests, and not while offline

    A GET can run twice without harm; a POST that charges a card cannot. Back off between tries, and stop when the browser reports it is offline.

    TypeScript
    async function fetchWithRetry(url: string, tries = 3) {  for (let attempt = 1; ; attempt++) {    try {      return await fetch(url); // GET only: safe to repeat    } catch (error) {      if (attempt >= tries || !navigator.onLine) throw error;      await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 250));    }  }}

Catch it in production

The Squasher browser SDK sends this error to your project when it happens, so you do not wait for a user to report it.

  • An uncaught fetch rejection is recorded as UnhandledRejection(TypeError), with the page URL, release, environment, and session ID.
  • When you catch the error yourself, pass it to captureError with the endpoint and navigator.onLine. Each event then says which call failed and whether the browser was online.
  • Repeats of the same error become one issue with an event count, so a jump after a deploy stands out.
src/api/profile.ts
import { init, captureError } from "@squasher-ai/browser"; init({  apiKey: import.meta.env.VITE_SQUASHER_API_KEY,  projectId: import.meta.env.VITE_SQUASHER_PROJECT_ID,  environment: import.meta.env.MODE,  release: import.meta.env.VITE_GIT_SHA,}); try {  await fetch("/api/profile");} catch (error) {  captureError(error as Error, {    endpoint: "/api/profile",    online: navigator.onLine,  });}
app.squasher.ai/acme/checkout-web/issues

TypeError: Failed to fetch

TypeError

MediumUnresolvedError
Events
312
First seen
2d ago
Last seen
4m ago
Environment
production
Service
checkout-web

Example code and issue.

JavaScript error monitoring with Squasher

Catch the next one first.

Send browser errors to Squasher and fix them before the support ticket arrives.

Free plan. No credit card required.