
네트워크 탭에서 API 디버깅
API 응답이 이상한데 코드 문제인지 서버 문제인지 구분이 안 될 때, 브라우저 네트워크 탭 하나로 원인을 찾는 방법.

API 응답이 이상한데 코드 문제인지 서버 문제인지 구분이 안 될 때, 브라우저 네트워크 탭 하나로 원인을 찾는 방법.
매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

HTTP는 무전기(오버) 방식이지만, 웹소켓은 전화기(여보세요)입니다. 채팅과 주식 차트가 실시간으로 움직이는 기술적 비밀.

IP는 이사 가면 바뀌지만, MAC 주소는 바뀌지 않습니다. 주민등록번호와 집 주소의 차이. 공장 출고 때 찍히는 고유 번호.

학교에서는 OSI 7계층을 배우지만, 실제 인터넷은 TCP/IP 4계층으로 돌아갑니다. 이론과 실제 차이.

프로젝트 중간에 이런 상황이 있었다. 백엔드 팀에서 만든 API를 Postman으로 테스트하면 완벽하게 동작한다. 200 OK, 데이터도 정확하게 온다. 그런데 실제 웹 애플리케이션에서 fetch로 호출하면 실패한다. 콘솔에는 CORS error가 찍히고, 데이터는 undefined로 돌아온다.
처음엔 내 코드 문제라고 생각했다. fetch 문법을 다시 확인하고, async/await 순서를 바꿔보고, try-catch를 추가해봤다. 그래도 똑같은 에러. 백엔드 팀에게 물어보니 "Postman에서 되는데요?"라는 답변만 돌아왔다.
결국 브라우저 개발자 도구의 Network 탭을 열어서 실제로 무슨 일이 일어나는지 들여다봤다. 그리고 발견했다. 요청 자체는 제대로 전송됐는데, 서버에서 보낸 응답 헤더에 Access-Control-Allow-Origin이 빠져 있었다. Postman은 브라우저가 아니라서 CORS를 체크하지 않았던 거였다. 문제는 내 코드가 아니라 서버 설정이었다.
이때 깨달았다. Network 탭은 코드와 서버 사이의 진실을 보여주는 블랙박스다. 코드가 "요청을 보냈다"고 주장하고, 서버가 "정상 응답했다"고 주장할 때, Network 탭은 실제로 무슨 일이 벌어졌는지 거짓 없이 보여준다.
Network 탭을 열면 모든 HTTP 요청이 시간순으로 나열된다. 각 요청을 클릭하면 오른쪽에 상세 정보가 뜬다. 여기서 중요한 탭들이 있다.
Headers 탭은 요청과 응답의 여권같은 곳이다. 어디서 왔고, 어떤 자격으로, 무엇을 원하는지가 적혀 있다.
Request Headers에서 확인할 것들:
Authorization: 인증 토큰이 제대로 붙었는지Content-Type: application/json인지 application/x-www-form-urlencoded인지Origin: 브라우저가 자동으로 붙인 출처 정보 (CORS 판단 기준)Response Headers에서 확인할 것들:
Access-Control-Allow-Origin: CORS 허용 여부Content-Type: 서버가 보낸 데이터 형식 (JSON인데 HTML로 왔다면 에러 페이지일 가능성)Set-Cookie: 쿠키가 제대로 설정됐는지// 예시: Authorization 헤더를 붙여서 요청
fetch('/api/user/profile', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
Network 탭에서 이 요청을 클릭하면 Request Headers에 Authorization: Bearer eyJhbGc...가 실제로 전송됐는지 확인할 수 있다. 코드에선 분명 토큰을 넣었는데 실제론 null이 전송되는 경우도 있다. 변수 스코프 문제거나 타이밍 문제인 경우가 많았다.
Preview는 JSON을 예쁘게 포맷팅해서 보여준다. 트리 구조로 펼쳐볼 수 있어서 깊게 중첩된 데이터를 확인하기 좋다.
Response는 날것 그대로의 응답이다. 서버가 JSON 대신 HTML 에러 페이지를 보냈다면 여기서 발견된다.
한번은 API가 { "data": null }을 응답하는데 왜 null인지 몰라서 삽질했다. Response 탭을 보니 실제로는 { "data": null, "error": "Invalid token" }이었다. 코드에서 response.data만 보고 있어서 에러 메시지를 놓쳤던 거다.
Timing 탭은 요청의 생애주기를 초 단위로 쪼개서 보여준다. 마치 육상 경기 슬로우 모션처럼.
페이지가 느리다고 느껴질 때 Timing을 보면 원인을 알 수 있다. TTFB가 5초면 서버가 느린 거고, Content Download가 5초면 네트워크나 데이터 크기 문제다.
// 예시: 느린 API 호출
async function loadUserData() {
const start = performance.now();
const response = await fetch('/api/users/list');
const data = await response.json();
const end = performance.now();
console.log(`Total time: ${end - start}ms`);
// Network 탭 Timing과 비교해서 어디가 느린지 확인
}
상태 코드는 서버의 표정이다. 같은 400번대라도 의미가 다르다.
실제로 헷갈렸던 경우: 로그인 후 대시보드 접근 시 계속 401이 나왔다. 로그인은 성공했는데 왜 401인가? Network 탭을 보니 로그인 API는 200이었지만, 그 다음 대시보드 API 요청 헤더에 토큰이 없었다. 로그인 후 토큰을 localStorage에 저장하는 코드가 비동기 타이밍 문제로 늦게 실행됐던 거다.
웹 페이지 하나 로드하면 수십 개의 요청이 날아간다. 이미지, CSS, JS, 폰트, 애널리틱스... 이 중에서 내가 보고 싶은 API만 보려면 필터를 사용한다.
Fetch/XHR 필터를 클릭하면 API 요청만 보인다/api/user) 해당 요청만 필터링된다페이지가 리다이렉트되거나 새로고침되면 Network 탭이 초기화된다. Preserve log 체크박스를 켜면 페이지 전환 후에도 이전 요청들이 남아 있다.
로그인 후 리다이렉트되는 플로우를 디버깅할 때 필수다. 로그인 POST 요청이 성공했는지, 그 다음에 어떤 요청이 실패했는지 전체 흐름을 볼 수 있다.
요청을 우클릭하고 Copy > Copy as cURL을 선택하면 터미널에서 실행 가능한 cURL 명령어가 복사된다. 헤더, 쿠키, 바디까지 전부 포함돼서 똑같은 요청을 터미널에서 재현할 수 있다.
백엔드 팀에게 버그 리포트할 때 이걸 보내면 "어떻게 요청했는데요?"라는 핑퐁을 줄일 수 있다. 정확히 같은 요청을 그대로 실행해볼 수 있으니까.
# Copy as cURL 예시 (실제 출력)
curl 'https://api.example.com/users/123' \
-H 'Authorization: Bearer eyJhbGc...' \
-H 'Content-Type: application/json' \
--compressed
Network 탭 상단에 Throttling 드롭다운이 있다. Fast 3G, Slow 3G 같은 옵션을 선택하면 인위적으로 네트워크를 느리게 만든다.
이미지 로딩이 느린 환경에서 UI가 어떻게 보이는지, API 응답이 5초 걸릴 때 사용자 경험이 어떤지 테스트할 수 있다. 로딩 스피너가 제대로 보이는지, 타임아웃 처리가 되는지 확인할 때 유용하다.
코드는 "보냈다"고 말하고, 서버는 "받았다"고 말할 수 있다. 하지만 실제로 무슨 일이 일어났는지는 Network 탭만 안다.
API 디버깅의 핵심은 추측이 아니라 관찰이다. "아마 헤더가 안 붙었을 거야"가 아니라 Headers 탭을 열어서 실제로 확인하는 것. "서버가 느린가봐"가 아니라 Timing 탭을 보고 TTFB를 측정하는 것.
Network 탭은 형사의 블랙박스 같은 도구다. 사건 현장을 정확히 재구성해준다. Headers, Preview, Response, Timing 탭을 순서대로 보면서 요청이 어디서 어떻게 실패했는지, 또는 성공했지만 왜 예상과 다른 결과가 나왔는지 추적할 수 있다.
다음에 "Postman에선 되는데 브라우저에선 안 돼요"라는 말을 들으면, 바로 Network 탭을 열어보자. 답은 거기에 있다.