Fix "Failed to Fetch" Error in JavaScript — CORS, HTTPS, and Network Issues
TypeError: Failed to fetch is a generic browser error that masks the real problem. It can mean CORS block, network offline, HTTPS mixed content, DNS failure, SSL certificate error, or server down. This guide covers every cause with a diagnostic checklist and specific fixes for each scenario.
6+
possible root causes covered
CORS
most common cause in development
Network tab
diagnose in 30 seconds
100%
fixable once the cause is identified
The error message
Diagnose in 30 Seconds
Open DevTools → Network tab
Reproduce the error with DevTools open. Find the failed request. It will show in red or as "(failed)" in the Status column.
Read the Status column
Red "(failed)" with no status code = browser blocked it before the server. 4xx/5xx status code = server received it and returned an error. No entry at all = request was never sent (code error or extension blocking).
Read the Console message
The console message immediately after "Failed to fetch" usually specifies the cause: "blocked by CORS policy", "Mixed Content", "ERR_NAME_NOT_RESOLVED", "ERR_CONNECTION_REFUSED", or "SSL_ERROR".
Check the request URL
Is it http:// on an https:// page? Is the hostname correct? Is it localhost when you're on a deployed domain? URL misconfiguration causes 40%+ of these errors.
Try the same URL in curl
Run: curl -v https://your-api/endpoint. If curl succeeds but browser fails, it's a browser security restriction (CORS, mixed content, or extension) rather than a server issue.
Test in incognito mode
Open an incognito/private window which disables browser extensions. If the request succeeds in incognito, an extension (ad blocker, VPN, privacy tool) is the cause.
The key insight
"Failed to fetch" is a browser-level error thrown when the fetch() call itself cannot complete. The browser logs the real cause in the console — always read the full console error, not just the first line. The Network tab shows you what actually happened to the request.
Cause 1 — CORS Policy Block
The most common cause in development. The browser blocks the response because the server did not include the required CORS headers. Note: the request often does reach the server — the browser blocks the response.
No CORS headers → browser blocks response
// Console shows: "blocked by CORS policy: No 'Access-Control-Allow-Origin' header"
fetch('https://api.example.com/data')
.then(r => r.json())
.catch(e => console.error(e)); // "Failed to fetch"
// IMPORTANT: The request DID reach the server.
// The browser blocked reading the response.Add CORS headers on server or use a proxy
// Fix 1: Add CORS headers on the server (Express):
const cors = require('cors');
app.use(cors({
origin: 'https://yourapp.com', // specific origin (not *)
credentials: true, // if using cookies
}));
// Fix 2: Dev proxy — route through same origin (no CORS needed):
// next.config.js:
rewrites: () => [{ source: '/api/:p*', destination: 'http://localhost:8000/api/:p*' }]
// Fix 3: Add OPTIONS handler for preflight:
app.options('*', cors()); // handle preflight for all routesCause 2 — Mixed Content (HTTP on HTTPS Page)
Mixed content error in console
http:// API URL on https:// page
// Your page is HTTPS but API is HTTP → blocked by browser
fetch('http://api.example.com/data') // ❌ http on https page
.then(r => r.json())
// TypeError: Failed to fetchUpgrade API to HTTPS
// Fix 1: Use HTTPS for your API
fetch('https://api.example.com/data') // ✅
// Fix 2: Use environment variable (set to https:// in production)
const API = process.env.NEXT_PUBLIC_API_URL; // "https://api.example.com"
fetch(`${API}/data`)
// Fix 3: If you can't upgrade the API to HTTPS, use a reverse proxy
// Put Nginx/Cloudflare in front to terminate SSLCause 3 — Network / DNS Failure
The server is unreachable — wrong URL, server down, DNS not resolving, or user is offline. The request never makes it to the server.
// Detect which type of network error occurred
async function fetchWithDiagnosis(url, options = {}) {
try {
const res = await fetch(url, options);
return res;
} catch (err) {
if (!navigator.onLine) {
throw new Error('You are offline. Please check your internet connection.');
}
// Check if it's a DNS failure vs. server down
if (err.message.includes('ERR_NAME_NOT_RESOLVED')) {
throw new Error(`Cannot resolve hostname: ${new URL(url).hostname}`);
}
if (err.message.includes('ERR_CONNECTION_REFUSED')) {
throw new Error(`Server is not running at: ${url}`);
}
throw err; // Re-throw unknown errors
}
}
// Retry with exponential backoff:
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url, options);
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); // 1s, 2s, 4s
console.log(`Retry ${i + 1}/${retries} after network error...`);
}
}
}
// Listen for online/offline events:
window.addEventListener('offline', () => showToast('You are offline.'));
window.addEventListener('online', () => showToast('Back online — retrying...'));Cause 4 — Request Cancelled by AbortController
AbortError indistinguishable from network errors
// Timeout set too short — request aborted before completing
const controller = new AbortController();
setTimeout(() => controller.abort(), 100); // 100ms is too short!
fetch('/api/slow-endpoint', { signal: controller.signal })
.catch(e => console.error(e)); // AbortError treated same as "Failed to fetch"Check err.name === 'AbortError' separately
// Handle AbortError separately from network errors
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10_000); // 10 seconds
fetch('/api/endpoint', { signal: controller.signal })
.then(r => r.json())
.catch(err => {
if (err.name === 'AbortError') {
showError('Request timed out after 10 seconds. Please try again.');
return null; // handle gracefully
}
throw err; // re-throw non-abort errors
})
.finally(() => clearTimeout(timeoutId));Cause 5 — SSL Certificate Error
Self-signed or expired SSL certificates cause browsers to block fetch requests. This is common in local development and staging environments with self-signed certificates.
# Option 1: mkcert — create trusted local certificates (recommended)
# macOS:
brew install mkcert
mkcert -install # install local CA into system trust store
mkcert localhost 127.0.0.1 # generate cert trusted by your browser
# Use the cert in your dev server (e.g., Next.js):
# NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem" next dev
# Option 2: Use HTTP in local dev only (add HTTPS in production)
# .env.local:
# NEXT_PUBLIC_API_URL=http://localhost:8000
# .env.production:
# NEXT_PUBLIC_API_URL=https://api.yourdomain.com
# Option 3: In Node.js server-side fetch only (NEVER in browser/production):
# process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; # ⚠️ insecure!Cause 6 — Browser Extension or Firewall Blocking
Quick test: incognito mode
Ad blocker blocking API domain
Tracking protection lists sometimes block analytics APIs, A/B testing endpoints, or any URL containing "track", "analytics", or "pixel". Whitelist your domain in the extension settings.
VPN extension
VPN extensions can block or reroute requests to certain IPs. Some corporate VPNs block outbound requests to non-approved domains.
CORS Unblock / Allow CORS extension
Ironically, "CORS Unblock" extensions sometimes cause "Failed to fetch" by modifying request headers in a way that confuses the server. Disable it and fix CORS properly on the server.
Browser security policies
Chrome's COEP/COOP headers or Firefox security settings can block certain cross-origin requests. Check for "ERR_BLOCKED_BY_RESPONSE" in the Network tab.
Quick Diagnostic Reference
| Item | Symptom in Network Tab | Likely Cause and Fix |
|---|---|---|
| No entry at all | Request was never sent | Code error (conditional guard), or extension blocking before network |
| Status: (failed), no CORS msg | Network/SSL/DNS error | Server down, wrong URL, cert error — try curl to confirm |
| Status: (failed) + CORS in console | CORS policy block | Add Access-Control-Allow-Origin header on server |
| Mixed content error in console | HTTP API on HTTPS page | Change API URL to https:// |
| Works in curl, fails in browser | Browser security policy | CORS, mixed content, or extension — check each in order |
| Works in incognito, fails normally | Browser extension blocking | Identify and disable extensions one by one |
| AbortError in catch block | Request cancelled by timeout | Increase timeout or check if component unmounted |