---
title: "Failed to fetch 오류를 고치고 원인 3가지를 구분하는 방법"
description: "TypeError: Failed to fetch는 요청이 HTTP 응답을 받지 못했다는 뜻이에요. CORS 헤더 누락, 꺼진 서버, 없는 호스트가 모두 같은 메시지를 내요."
canonical: "https://logbrew.co/ko/errors/failed-to-fetch"
last_updated: "2026-11-10"
---

# Failed to fetch 오류를 고치고 원인 3가지를 구분하는 방법

`TypeError: Failed to fetch`는 요청이 HTTP 응답을 받지 못했다는 뜻이에요. CORS 헤더 누락, 꺼진 서버, 없는 호스트가 모두 같은 메시지를 내요.

Chromium 152, @logbrew/browser 0.1.21 및 @logbrew/sdk 0.1.44 환경에서 2026년 11월 10일에 테스트

글 [Furkan Erday](https://github.com/furkanerday). LogBrew 창업자 Furkan Erday. 매일 Claude Code와 Codex로 LogBrew를 만들어요.

Canonical: https://logbrew.co/ko/errors/failed-to-fetch

## 실패한 `fetch()`의 원인을 알려 주는 곳

| 무엇이 잘못됐는지 | `error.message` | 콘솔 |
| --- | --- | --- |
| `Access-Control-Allow-Origin` 헤더 없음 | `Failed to fetch` | CORS 정책 |
| 포트에서 듣는 서버 없음 | `Failed to fetch` | `ERR_CONNECTION_REFUSED` |
| 호스트에 DNS 레코드 없음 | `Failed to fetch` | `ERR_NAME_NOT_RESOLVED` |
| 서버가 500으로 응답 | 거부 안 함 | `500` |

다른 출처의 API에 요청 4개를 보냈어요. 가득 찬 머그잔은 그 텍스트가 원인을 알려 준다는 뜻이에요.

## 어떤 원인인지 찾기

### 브라우저 콘솔 열기

`fetch()`는 네트워크 실패마다 같은 `TypeError: Failed to fetch`로 거부해요. 그 옆의 콘솔 줄이 실제 원인을 알려 줘요.

### API가 CORS 헤더 없이 응답함

콘솔에 `blocked by CORS policy`가 떠요. 서버는 응답했지만 페이지 출처가 응답을 읽을 권한이 없어서 브라우저가 응답을 버려요.

출력

```text
Access to fetch at 'http://127.0.0.1:4874/api/closed' from origin 'http://127.0.0.1:4873' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```

### 그 호스트와 포트에서 듣는 서버가 없음

콘솔에 `net::ERR_CONNECTION_REFUSED`가 떠요. API가 실행 중이 아니거나 URL의 포트가 틀렸어요. API를 시작한 뒤 포트를 확인하세요.

출력

```text
Failed to load resource: net::ERR_CONNECTION_REFUSED
```

### 호스트 이름을 찾을 수 없음

콘솔에 `net::ERR_NAME_NOT_RESOLVED`가 떠요. DNS에 그 호스트의 레코드가 없어요. 호스트 이름에 오타가 없는지, 이 배포에서 API URL을 담은 환경 변수를 확인하세요.

출력

```text
Failed to load resource: net::ERR_NAME_NOT_RESOLVED
```

## 코드에서 고치기

### API에서 CORS 헤더 보내기

`Access-Control-Allow-Origin`을 페이지 출처로, 공개 API면 `*`로 설정해 응답하세요. 테스트에서는 같은 엔드포인트가 이 헤더 하나만 더해서 `Failed to fetch`에서 `HTTP 200`으로 바뀌었어요.

```bash
Access-Control-Allow-Origin: https://app.example.com
```

### 500은 거부되지 않으니 상태 확인하기

`fetch()`는 404와 500을 포함해 어떤 HTTP 응답에도 이행돼요. 테스트에서 본문이 `{"error":"tap jammed"}`인 500은 `catch`가 아니라 `then`으로 갔어요. 본문을 읽기 전에 `response.ok`를 확인하세요.

```bash
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status} for ${url}`);
```

### 오류와 함께 URL 기록하기

거부된 `fetch()`에는 응답이 없고, 스택은 원인이 아니라 내 코드를 가리켜요. `fetch()`를 호출한 곳에서 잡아서 메서드와 URL을 오류와 함께 남기면 오류 추적에서 어떤 요청이 실패했는지 보여요.

```bash
try {
  return await fetch(url);
} catch (error) {
  console.error("fetch failed", url, error);
  throw error;
}
```

## 직접 테스트해 보니

페이지는 `@logbrew/browser` 0.1.21과 fetch 계측을 설치한 Chromium 152에서 `http://127.0.0.1:4873`으로 실행했어요. `http://127.0.0.1:4874`의 API가 경로 3개에 응답했고, `catch` 없이 요청 5개를 보내서 거부가 모두 처리되지 않은 거부로 SDK에 도달하게 했어요.

`/api/closed`는 CORS 헤더 없이 200으로 응답했고 요청은 `TypeError: Failed to fetch`로 거부됐어요. 아무것도 듣지 않는 4875 포트로 보낸 요청과 `http://keg.invalid/api`로 보낸 요청도 같은 오류로 거부됐어요. 3가지 모두 `error.message`는 `Failed to fetch`, `error.name`은 `TypeError`였고, 스택은 직접 호출한 `fetch()`를 가리켰어요.

콘솔이 셋을 구분해 줬어요. 첫 번째는 `blocked by CORS policy`, 두 번째는 `net::ERR_CONNECTION_REFUSED`, 세 번째는 `net::ERR_NAME_NOT_RESOLVED`였어요. 헤더와 함께 500으로 응답한 `/api/broken`은 전혀 거부되지 않았고, 헤더와 함께 200으로 응답한 `/api/open`은 `HTTP 200 {"ok":true}`를 줬어요.

## 모든 원인이 같은 메시지를 내는 이유

Fetch 표준은 응답 전의 모든 실패를 1가지, 즉 타입이 `error`이고 상태가 0인 응답인 네트워크 오류로 바꿔요. CORS 실패도 그중 하나예요. `cors` 모드에서 CORS 프로토콜을 이해하지 못하는 리소스는 네트워크 오류를 내요.

`fetch()` 메서드는 네트워크 오류일 때만 프로미스를 `TypeError`로 거부하고, 다른 경우에는 거부하지 않아요. MDN도 같은 말을 해요. 프로미스는 잘못된 URL이나 네트워크 오류처럼 요청 자체가 실패할 때만 거부되고, 404나 504 같은 HTTP 상태에서는 거부되지 않아요.

MDN의 CORS 오류 문서는 실패한 CORS 요청의 세부 정보가 보안상의 이유로 JavaScript에 제공되지 않고, 콘솔이 그걸 볼 수 있는 유일한 곳이라고 설명해요. whatwg/fetch#526은 2017년부터 더 자세한 오류 타입을 요청해 왔고 아직 열려 있어요.

## LogBrew가 기록하는 것

거부는 각각 `Unhandled promise rejection: TypeError`라는 이슈 1개로 도착했고, 번들 안을 가리키는 스택 프레임과 그룹화 키 `browser.unhandledrejection:TypeError:/app.js`가 담겼어요. 이걸로 `fetch()`를 어디서 호출했는지는 알 수 있지만 무엇이 실패했는지는 알 수 없어요.

fetch 계측은 같은 트레이스에 `browser.fetch GET /api/closed` 스팬을 더했고, 상태 `error`, 메서드, 요청 경로, `errorType` `TypeError`를 담았어요. 오류는 알려 주지 않는 실패한 요청을 스팬이 알려 줘요.

500 응답은 이슈를 만들지 않았고, 상태 `error`와 `statusCode` 500인 스팬만 남겼어요. 200 응답은 상태 `ok`인 스팬을 남겼어요. 그래서 LogBrew에서는 500과 네트워크 실패가 서로 다른 스팬 2개지만, 코드에서는 두 번째만 `catch`에 도달해요.

## 자주 묻는 질문

### TypeError Failed to fetch는 무슨 뜻인가요?

브라우저가 `fetch()` 호출에 대한 HTTP 응답을 받지 못했다는 뜻이에요. 서버에 연결할 수 없거나, 호스트를 찾지 못했거나, CORS가 응답을 막은 거예요. 3가지 경우 모두 메시지가 같아요.

### Failed to fetch는 항상 CORS 오류인가요?

아니요. 테스트에서는 CORS 헤더 누락, 서버가 없는 포트, DNS 레코드가 없는 호스트가 모두 이 메시지를 냈어요. 옆에 있는 콘솔 줄만 원인을 알려 줘요.

### fetch는 왜 404나 500에서 거부되지 않나요?

Fetch 표준은 네트워크 오류일 때만 거부해요. 상태가 무엇이든 HTTP 응답은 응답이라서 `fetch()`는 이행되고, `response.ok`는 직접 확인해야 해요.

### JavaScript에서 Failed to fetch는 어떻게 잡나요?

호출을 `try`와 `catch`로 감싸거나 프로미스에 `.catch()`를 붙이세요. 오류는 응답이 없는 `TypeError`라서 URL과 메서드는 직접 기록해야 해요.

### Failed to fetch는 서버가 꺼졌다는 뜻인가요?

그럴 때도 있어요. 서버가 꺼져 있으면 콘솔에 `net::ERR_CONNECTION_REFUSED`가 떠요. 같은 `Failed to fetch`는 CORS 헤더 누락이나 DNS 레코드가 없는 호스트 이름에서도 나와요.

## 어떤 요청이 왜 실패했는지 보기

LogBrew 브라우저 SDK는 `fetch()`마다 메서드, 경로, 상태를 담은 스팬을 기록하고, 그 요청이 일으킨 오류와 같은 트레이스에 넣어요. 넉넉한 무료 요금제로 시작하세요. 카드 등록은 필요 없어요.

[무료로 시작](https://logbrew.co/ko)

## 출처

- [MDN, Window: fetch() method](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)
- [MDN, CORS errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors)
- [Fetch Standard, The fetch() method](https://fetch.spec.whatwg.org/#fetch-method)
- [whatwg/fetch#526](https://github.com/whatwg/fetch/issues/526)

## JavaScript 오류 해결법 더 보기

- [메시지와 파일, 줄을 숨기는 스크립트 오류 Script error 해결하기](https://logbrew.co/ko/errors/script-error)
- [직접 재현한 JavaScript 오류와 각각의 해결법](https://logbrew.co/ko/errors)
