본문 바로가기

고급 디버깅 기법

콘솔 API·중단점·단계 실행·Watch·Network·Memory 패널로 실행 흐름과 데이터·요청·누수 문제를 추적합니다.

앞 글들에서는 단위, 통합, E2E 테스트와 크로스 브라우저 테스팅으로 품질을 높이는 방법을 익혔습니다. 테스트는 많은 버그를 미리 걸러 주지만 모든 경우와 환경을 빠짐없이 다룰 수는 없어서, 예상하지 못한 조건이나 특정 사용자 환경에서만 드러나는 버그는 여전히 생깁니다. 그때 필요한 것이 문제의 원인을 찾아 고치는 디버깅(debugging)입니다.

디버깅은 코드가 실행되는 흐름을 따라가고, 변수의 값을 살피고, 오류가 생기는 지점을 정확히 찾아 고치는 과정입니다. 이 글에서는 console.log()를 넘어, 브라우저 개발자 도구의 여러 기능으로 복잡한 문제를 효율적으로 진단하는 방법을 정리합니다. 화면의 메뉴 이름은 Chrome 개발자 도구를 기준으로 하며, 브라우저와 버전에 따라 조금 다를 수 있습니다.

증상에 맞는 개발자 도구 증거를 고른다

EVIDENCE MAP

증상에 맞는 개발자 도구 증거를 고른다

문제를 같은 URL·입력·환경에서 재현한 뒤 아래 패널을 조합해 가설을 확인한다. 한 증상이 여러 원인 경계에 걸칠 수 있다.

증상과 먼저 확인할 증거
증상패널확인할 증거
요청·응답 이상NetworkURL·status·headers·응답 본문·waterfall을 같은 캐시 조건에서 비교한다.
예외·상태 이상Sources + Console중단점의 Scope·Watch·호출 스택으로 기대값과 실제값을 비교한다.
DOM·스타일 이상Elements계산된 스타일·box model·DOM 변경 중단점으로 화면과 코드의 관계를 확인한다.
느린 로딩·반응Performance같은 상호작용을 기록하고 main thread·long task·layout·paint 비용을 분리한다.
반복 후 메모리 증가Memory반복 전후 heap snapshot과 보유 경로를 비교한다. 살아 있는 객체가 모두 누수인 것은 아니다.

요청·응답 이상 · Network

URL·status·headers·응답 본문·waterfall을 같은 캐시 조건에서 비교한다.

예외·상태 이상 · Sources + Console

중단점의 Scope·Watch·호출 스택으로 기대값과 실제값을 비교한다.

DOM·스타일 이상 · Elements

계산된 스타일·box model·DOM 변경 중단점으로 화면과 코드의 관계를 확인한다.

느린 로딩·반응 · Performance

같은 상호작용을 기록하고 main thread·long task·layout·paint 비용을 분리한다.

반복 후 메모리 증가 · Memory

반복 전후 heap snapshot과 보유 경로를 비교한다. 살아 있는 객체가 모두 누수인 것은 아니다.

특정 기기에서만 나타나면 해당 기기·브라우저 버전에서 원격 디버깅으로 같은 증거를 수집한다. 사용할 수 있는 패널은 브라우저와 연결 환경에 따라 다르다.

가설 하나 수정 → 같은 조건으로 재검증

증거를 바탕으로 원인을 좁히고 최소 변경을 적용한다. 같은 조건에서 전후 결과를 비교하고 주변 동작의 회귀도 확인한다.

위 표는 증상에 따라 먼저 열어 볼 개발자 도구의 패널과 확인할 정보를 정리한 것입니다. 시작하기 전에 같은 URL, 같은 입력, 같은 환경에서 문제를 다시 만들어 두고, 하나의 증상이 여러 원인에 걸쳐 있을 수 있으므로 패널을 조합해 가설을 확인합니다. 요청이나 응답이 이상하면 Network 패널에서 URL, 상태 코드, 헤더, 응답 본문, 요청이 진행된 시간표(waterfall)를 같은 캐시 조건에서 비교합니다. 예외가 나거나 상태가 이상하면 Sources와 Console에서 중단점을 걸고 Scope, Watch, 호출 스택으로 기대한 값과 실제 값을 비교합니다.

DOM이나 스타일이 이상하면 Elements 패널에서 계산된 스타일과 박스 모델을 보고, DOM 변경 중단점으로 화면과 코드의 관계를 확인합니다. 로딩이나 반응이 느리면 Performance 패널로 같은 동작을 기록해 메인 스레드의 긴 작업과 레이아웃, 페인트 비용을 나눠 보고, 같은 동작을 반복할수록 메모리가 늘면 Memory 패널에서 반복 전후의 힙 스냅샷과 객체를 붙잡은 참조 경로를 비교합니다. 특정 기기에서만 나타나는 문제는 그 기기와 브라우저 버전에 원격 디버깅으로 연결해 같은 정보를 모읍니다. 표 아래의 설명처럼 정보를 바탕으로 원인을 좁혀 한 번에 가설 하나만 고치고, 같은 조건에서 전후를 비교하며 주변 동작이 깨지지 않았는지도 확인합니다.


console.log()를 넘어서는 콘솔 API

console.log()는 가장 흔하고 쉬운 디버깅 도구이지만, console 객체에는 더 많은 메서드가 있습니다.

console.warn()은 경고 수준의, console.error()는 오류 수준의 메시지를 출력합니다. 아이콘과 색은 브라우저와 테마에 따라 다르고 로그 수준 필터로 숨길 수도 있으며, console.error()는 메시지를 남길 뿐 예외를 던지거나 실행을 멈추지는 않습니다. console.info()는 정보성 메시지로 대개 console.log()처럼 동작합니다.

console.dir()은 객체의 속성을 트리로 자세히 보여 줍니다. console.log()가 DOM 요소를 HTML처럼 보여 주는 것과 달리, console.dir()은 DOM 요소도 자바스크립트 객체처럼 보여 주므로 요소의 속성을 살필 때 유용합니다. console.table()은 배열이나 객체 배열을 표로 출력해 데이터의 구조를 한눈에 보여 줍니다.

console.count(label)은 같은 레이블로 호출된 횟수를 세어 출력하고, console.time(label)과 console.timeEnd(label)은 두 호출 사이의 시간을 잽니다. console.trace()는 지금 위치까지의 호출 스택(stack trace)을 출력하고, console.group(label)과 console.groupEnd()는 그 사이의 메시지를 하나의 묶음으로 들여 써서 보여 줍니다.

function processData(data) {
    console.group('데이터 처리 과정'); // 그룹 시작

    console.log('원본 데이터:', data);

    if (!Array.isArray(data)) {
        console.error('입력 데이터는 배열이어야 합니다.', data);
        console.trace('여기서 오류가 발생했습니다.'); // 호출 스택 추적
        console.groupEnd(); // 그룹 종료
        return null;
    }

    const processed = data.map((item, index) => {
        console.count('아이템 처리'); // 각 아이템 처리 횟수 카운트
        return item * 2;
    });

    console.table(processed); // 처리된 데이터를 표로 출력

    console.time('반복 처리 시간'); // 시간 측정 시작
    for (let i = 0; i < 100000; i++) { /* 복잡한 연산 시뮬레이션 */ }
    console.timeEnd('반복 처리 시간'); // 시간 측정 종료

    console.groupEnd(); // 그룹 종료
    return processed;
}

const myNumbers = [1, 2, 3, 4, 5];
processData(myNumbers);
processData("Not an array");

이 예제는 숫자 배열을 넣는다고 가정합니다. 첫 호출은 [2, 4, 6, 8, 10]을, 두 번째 호출은 배열이 아니라서 null을 돌려줍니다. Array.isArray()는 배열인지만 확인할 뿐 요소가 숫자인지까지 검사하지는 않습니다. console.time()과 console.timeEnd()는 그 사이의 반복문만 감싸므로, processData() 전체의 실행 시간을 잰 것이 아닙니다. 각 메서드의 역할은 Chrome의 Console API 문서에서 확인할 수 있습니다.


중단점과 단계 실행

개발자 도구의 Sources 패널에서는 자바스크립트의 실행을 잠시 멈추고, 한 줄씩 실행하며, 그 순간의 변수 값을 살펴볼 수 있습니다. 디버깅에서 가장 많이 쓰는 기능입니다.

중단점 설정

Sources 패널에서 멈추고 싶은 줄의 번호를 클릭하면 중단점(breakpoint)이 설정됩니다. 실행은 그 줄을 실행하기 직전에 멈추므로, 그 줄의 대입 결과는 아직 변수에 들어가 있지 않습니다.

중단점을 오른쪽 클릭해 조건부 중단점(conditional breakpoint)을 추가하면, i === 5나 user.id === 123 같은 조건이 참일 때만 멈춥니다. 반복문이나 여러 번 호출되는 이벤트 핸들러에서 특정한 경우에만 멈추고 싶을 때 씁니다. 로그포인트(logpoint)를 추가하면 실행을 멈추지 않고, 그 지점에 도달할 때마다 지정한 메시지나 값을 콘솔에 출력합니다. 코드에 console.log()를 넣는 것과 비슷하지만 코드를 고치지 않아도 됩니다.

코드 줄이 아닌 사건에 거는 중단점도 있습니다. Elements 패널에서 요소를 오른쪽 클릭해 Break on 메뉴의 하위 트리 변경, 속성 변경, 노드 삭제 가운데 하나를 고르면, 그 요소에 해당 변경이 생길 때 실행이 멈춥니다. Sources 패널의 Event Listener Breakpoints에서 클릭 같은 이벤트를 고르면 그 이벤트가 생길 때 멈추므로 어떤 리스너가 호출되는지 찾기 쉽고, XHR/Fetch Breakpoints에서는 특정 URL이나 모든 요청을 보낼 때 멈추게 할 수 있습니다.

단계 실행 제어

중단점에서 멈추면 다음 버튼으로 실행을 조절합니다. Resume(F8)은 다음 중단점이나 스크립트 끝까지 계속 실행합니다. Step over(F10)는 지금 줄을 실행하고 다음 줄로 가며, 그 줄의 함수 호출은 안으로 들어가지 않고 한 번에 실행합니다. Step into(F11)는 그 줄에 함수 호출이 있으면 함수 안으로 들어가고, Step out(Shift+F11)은 지금 함수를 끝까지 실행한 뒤 그 함수를 부른 곳으로 돌아갑니다. Step(F9)은 다음에 실행할 코드로 한 단계 이동합니다.

Scope와 Watch 패널

실행이 멈춘 상태에서 Scope 패널은 지금 실행 컨텍스트의 지역 변수, 클로저 변수, 전역 변수를 모두 보여 줍니다. 6장에서 본 스코프 체인을 눈으로 확인할 수 있는 셈입니다. Watch 패널에는 계속 지켜볼 변수나 표현식을 추가해 둘 수 있습니다.

Watch에는 item.quantity처럼 값을 읽기만 하는 표현식을 넣습니다. 상태를 바꾸는 함수 호출을 넣으면, 값을 볼 때마다 그 함수가 실행되어 관찰하는 일 자체가 결과를 바꿔 버릴 수 있습니다. 중단점의 종류와 멈추는 시점은 Chrome의 중단점 문서에서 자세히 볼 수 있습니다.


Network 패널

웹 애플리케이션의 많은 문제는 서버와의 통신에서 생깁니다. Network 패널은 모든 HTTP 요청과 응답의 자세한 정보를 보여 줍니다.

요청을 클릭하면 헤더(Headers), 응답 미리보기(Preview), 응답 본문(Response), 시간(Timing)을 볼 수 있습니다. 상태 코드로 성공과 실패를 빠르게 확인하고, POST나 PUT 요청이라면 Payload에서 클라이언트가 서버에 보낸 데이터가 맞는지 확인합니다. Timing에서는 DNS 조회와 연결, 응답까지 걸린 시간을 나눠 보여 줘 느린 지점을 찾을 수 있습니다. 요청의 종류(Fetch/XHR, JS, CSS, Img 등)로 거르거나 URL로 검색할 수 있고, 네트워크 속도를 제한하는 Throttling으로 느린 모바일 네트워크나 오프라인 상태를 흉내 낼 수 있습니다.

HTTP 오류와 통신 실패는 나눠서 봐야 합니다. Fetch API와 AJAX에서 본 것처럼 fetch()는 서버가 404나 500 응답을 보냈다는 이유로 거부되지 않습니다. 응답을 받았다면 response.ok와 response.status, 본문을 확인하고, 연결 실패나 요청 취소로 거부되었다면 catch의 오류를 확인합니다. 그래서 catch가 실행되지 않았으니 성공했다고 판단하면 안 됩니다(MDN의 Fetch API 사용하기).


Performance 패널과 Memory 패널

복잡한 웹 애플리케이션에서 성능 저하나 메모리 누수는 사용 경험을 크게 해칩니다.

Performance 패널은 페이지가 실행되는 동안의 CPU 사용량, 네트워크 활동, 레이아웃 계산, 페인트를 기록해 시각적으로 보여 줍니다. 특정 시간 동안의 동작을 기록해 프레임이 끊기거나 오래 걸리는 자바스크립트 호출과 렌더링 작업을 찾고, Layout, Recalculate Style, Paint 같은 항목으로 렌더링 과정의 비효율을 확인합니다. 자세한 사용법은 다음 글에서 다룹니다.

Memory 패널은 메모리 사용량을 분석해 누수나 비효율적인 사용을 진단합니다. 힙 스냅샷(heap snapshot)은 한 시점의 자바스크립트 힙을 기록해, 어떤 객체가 메모리를 얼마나 차지하는지와 가비지 컬렉션 뒤에도 남은 객체를 누가 붙잡고 있는지 보여 줍니다. 할당 타임라인(allocation timeline)은 시간에 따라 메모리가 할당되고 해제되는 모습을 기록합니다. 메모리 관리와 가비지 컬렉션에서 본 것처럼 살아 있는 객체가 모두 누수인 것은 아니며, 더 필요 없는 객체가 참조 때문에 남아 있는지를 봐야 합니다.

예를 들어 모달을 열고 닫을 때 누수가 의심된다면, 안정된 처음 상태에서 스냅샷 A를 찍은 뒤 같은 열기와 닫기를 10번 반복하고 스냅샷 B를 찍습니다. 다시 10번 반복해 찍은 스냅샷 C와 비교해, 문서에서 떼어 낸 DOM 객체가 계속 늘어나는지, 어떤 이벤트 리스너나 컬렉션이 그 객체를 붙잡고 있는지 확인합니다. 의도적으로 유지하는 캐시도 있으므로, 전체 메모리가 늘었다는 것만으로 누수라고 결론 내리지 않습니다. 참조 경로를 보는 방법은 Chrome의 힙 스냅샷 문서에 있습니다.


그 밖의 디버깅 도구

소스 맵(source map)은 압축하거나 변환한 배포용 코드를 원래 개발 코드로 연결해, 개발자 도구에서 원래 코드로 디버깅할 수 있게 해 줍니다. webpack이나 Rollup 같은 빌드 도구가 설정에 따라 만들어 줍니다. Sources 패널의 Snippets에서는 자바스크립트 코드를 직접 작성해 실행할 수 있어, 함수를 빠르게 시험하거나 값을 바꿔 보는 실험에 쓸 수 있습니다.

코드에 debugger; 문을 넣으면, 개발자 도구가 열려 있을 때 그 자리에서 중단점처럼 실행이 멈춥니다. 잠깐 확인할 때 편리하지만, 배포하기 전에는 지워야 합니다.

function calculateTotal(items) {
    // debugger; // 여기에 브레이크포인트가 걸림
    let total = 0;
    for (const item of items) {
        total += item.price * item.quantity;
    }
    return total;
}

모바일 기기는 원격 디버깅으로 검사합니다. Android의 Chrome은 USB 디버깅을 허용한 기기를 데스크톱 Chrome에 연결해 검사할 수 있습니다(Chrome의 원격 디버깅 문서). iPhone이나 iPad, 시뮬레이터의 Safari는 Mac의 Safari 웹 속성 보기(Web Inspector)로 검사합니다(Apple의 iOS 검사 문서, WebKit의 Web Inspector 설정 안내). 데스크톱에서 화면 크기만 흉내 내서는 실제 모바일 브라우저의 성능이나 입력 문제를 모두 재현할 수 없습니다.

더 깊이: 소스 맵은 어떻게 원래 줄을 찾을까

소스 맵의 형식은 2024년 12월에 나온 ECMA-426 1판이 정하며, 이 표준은 자바스크립트 표준과 같은 TC39에서 만듭니다. 소스 맵 파일은 JSON이며, version은 언제나 3이고, sources에는 원래 파일들의 주소를, 선택 항목인 sourcesContent에는 원래 파일의 내용을, names에는 압축 전의 변수와 함수 이름을 담습니다. 핵심은 mappings 문자열로, 변환된 코드의 각 위치가 어느 원본 파일의 몇째 줄 몇째 칸에서 왔는지를 Base64 VLQ라는 짧은 가변 길이 숫자 형식으로 적어 둡니다.

브라우저는 변환된 파일에서 소스 맵의 위치를 두 가지 방법으로 찾습니다. 파일 끝에 //# sourceMappingURL=bundle.js.map 같은 주석이 있거나, 서버가 응답에 SourceMap 헤더로 주소를 알려 주는 것입니다. 둘이 함께 있으면 표준은 헤더를 먼저 쓰도록 정합니다. 개발자 도구는 이 파일을 읽어 오류의 위치와 중단점을 원래 코드의 줄로 바꿔 보여 줍니다.

배포할 때는 소스 맵이 무엇을 드러내는지도 생각해야 합니다. sourcesContent가 들어 있으면 소스 맵만 받아도 압축 전의 원래 코드와 주석을 그대로 읽을 수 있습니다. 그래서 공개 서버에는 소스 맵을 올리지 않고 오류 수집 서비스에만 올리거나, 내용을 빼고 만드는 식으로 빌드 설정을 정합니다. 형식의 정의는 ECMA-426 명세에 있습니다.


가설로 원인 좁히기

가설 기반 디버깅 검증 루프

PROCESS · B99 WEB FUNDAMENTALS

가설 기반 디버깅 검증 루프

같은 증상을 재현하고 반증 가능한 예측을 한 문장으로 세운다. 중단점에서 기대값과 실제값을 측정한 뒤 변수 하나만 바꾸고, 예측이 맞지 않으면 가설로 되돌아간다.

가설 기반 디버깅 검증 루프증상을 재현하고 반증 가능한 가설을 세운 뒤 breakpoint에서 기대값과 실제값을 측정하고 변수 하나를 바꿔 같은 조건에서 검증하는 루프 증상 재현입력 · 환경 · 기대 결과 고정반증 가능한 가설“원인이 X면 Y여야 한다”기대값 ↔ 실제값 측정Scope · Watch · call stack변수 하나만 변경가설과 직접 연결된 최소 수정같은 조건에서예측과 일치하는가?수정 + 회귀 테스트원인 · 증거 · 결과를 기록예측 불일치예측 일치
가설 기반 디버깅 검증 루프증상을 재현하고 반증 가능한 가설을 세운 뒤 breakpoint에서 기대값과 실제값을 측정하고 변수 하나를 바꿔 같은 조건에서 검증하는 루프. 모바일에서도 같은 노드와 분기·귀환을 세로로 배치한다. 증상재현 입력 · 환경 · 기대 결과 고정 반증 가능한 가설 “원인이 X면 Y여야 한다” 기대값 ↔ 실제값 측정 Scope · Watch · call stack 변수 하나만 변경 가설과 직접 연결된 최소 수정 같은 조건에서 예측과 일치하는가? 수정 + 회귀 테스트 원인 · 증거 · 결과를 기록 예측 일치 예측 불일치

위 그림은 가설을 세워 원인을 좁히는 디버깅 순환입니다. 먼저 입력, 환경, 기대한 결과를 고정해 같은 증상을 다시 만듭니다. 그다음 “원인이 X라면 Y가 관찰되어야 한다”처럼 틀렸는지 확인할 수 있는 가설을 한 문장으로 세우고, 중단점에서 Scope와 Watch, 호출 스택으로 기대한 값과 실제 값을 잽니다.

가설과 직접 이어진 부분 하나만 바꾼 뒤, 같은 조건에서 예측대로 결과가 나오는지 확인합니다. 예측과 맞으면 수정을 확정하고 회귀 테스트를 추가하며, 원인과 확인한 정보, 결과를 기록합니다. 예측과 맞지 않으면 바꾼 것을 되돌리고 가설을 세우는 단계로 돌아갑니다. 한 번에 여러 곳을 바꾸면 무엇이 효과가 있었는지 알 수 없으므로, 변수 하나만 바꾸는 것이 핵심입니다.

계산 오류를 입력까지 추적하기

앞의 calculateTotal()에 다음 입력을 넣어 봅니다. price와 quantity는 숫자여야 하고, 금액 단위는 원입니다. 브라우저 콘솔이나 Node.js에서 실행할 수 있습니다.

const responseItems = [
    { price: 1000, quantity: 2 },
    { price: 500, qty: 3 }, // API 응답이 예상한 필드명과 다름
];

console.log(calculateTotal(responseItems)); // NaN

먼저 “두 번째 항목에 quantity가 없어서 합계가 NaN이 된다”는 가설을 세우고, total += item.price * item.quantity; 줄에 조건부 중단점 item.quantity === undefined를 설정합니다. 가설이 맞다면 이 줄을 실행하기 직전에 멈췄을 때 total은 첫 항목의 합인 2000, item.price는 500, item.quantity는 undefined여야 합니다. Step over로 한 줄 실행하면 500 * undefined가 NaN이 되어 total도 NaN이 됩니다.

그다음 Network 패널의 응답 본문에서 실제 필드 이름이 qty인지 확인하고, 입력을 만드는 쪽에서 필드 이름을 맞춥니다. 이 예제가 약속한 필드가 quantity라면 두 번째 항목을 { price: 500, quantity: 3 }으로 고쳤을 때 합계는 3500입니다. 합계가 NaN일 때 무조건 0으로 바꾸면 잘못된 입력을 감출 뿐입니다. 정상 입력, 빈 배열, 문제가 있던 입력을 다시 실행해 결과와 실패를 처리하는 방식을 함께 확인합니다.


정리

  • 디버깅은 같은 증상을 재현하고, 증상에 맞는 개발자 도구 패널에서 정보를 모아 원인을 좁히는 과정입니다.
  • console에는 table(), count(), time(), trace(), group() 같은 메서드가 있으며, console.error()는 메시지만 남기고 실행을 멈추지 않습니다.
  • 중단점은 그 줄을 실행하기 직전에 멈추며, 조건부 중단점, 로그포인트, DOM 변경과 이벤트, 요청 중단점으로 원하는 순간에만 멈출 수 있습니다.
  • 단계 실행과 Scope, Watch 패널로 값을 따라가되, Watch에는 상태를 바꾸지 않는 표현식만 넣습니다.
  • Network 패널에서는 HTTP 오류와 통신 실패를 나눠 보고, Memory 패널에서는 반복 전후의 스냅샷과 참조 경로로 누수를 판단합니다.
  • 원인을 고칠 때는 틀렸는지 확인할 수 있는 가설을 세우고, 한 번에 하나만 바꿔 같은 조건에서 다시 확인합니다.

연습 문제

1. 두 번째 반복에서 멈추면

calculateTotal([{ price: 1200, quantity: 2 }, { price: 300, quantity: 4 }])을 실행하면서, 두 번째 반복의 합계 대입 줄에서 멈췄습니다. 그 줄을 실행하기 전의 total, 한 줄 실행한 뒤의 total, 최종 반환값은 각각 얼마일까요? 또 함수 안에서 console.error('잘못된 수량')만 호출하면 계산이 자동으로 멈출까요?

해설 보기

두 번째 반복 직전의 total은 1200 × 2 = 2400입니다. 한 줄 실행하면 2400 + 300 × 4 = 3600이 되고, 반복이 끝나면 3600을 돌려줍니다. 빈 배열을 넣으면 반복하지 않으므로 처음 값인 0을 돌려줍니다.

console.error()는 메시지를 기록할 뿐 실행을 멈추지 않습니다. 계산을 멈추려면 조건에 맞을 때 return하거나 예외를 던지는 흐름을 따로 만들어야 합니다.

2. 다시 실행한 count

processData(myNumbers)를 한 번 더 호출했습니다. 이번에 출력되는 아이템 처리의 횟수는 몇부터 시작할까요? 매번 1부터 세려면 어떻게 해야 할까요?

해설 보기

6부터 시작해 10까지 출력됩니다. console.count()의 횟수는 함수 호출이 끝나도 같은 레이블로 계속 이어지기 때문에, 앞의 호출에서 센 5에 이어서 셉니다.

실행마다 따로 세고 싶다면 호출 전에 console.countReset('아이템 처리')로 횟수를 초기화하면 됩니다. 두 실행의 횟수를 비교할 때는 이렇게 초기화한 뒤 측정해야 결과를 잘못 읽지 않습니다.

3. 오류가 보이지 않는 요청

Network 패널에서 /api/items 요청이 404로 표시되는데, 콘솔에는 아무 오류도 없고 코드의 catch 블록도 실행되지 않았습니다. 요청이 성공했다고 판단해도 될까요? 무엇을 확인해야 할까요?

해설 보기

성공했다고 판단하면 안 됩니다. fetch()는 404 응답을 받아도 거부되지 않고 response.ok가 false인 응답으로 이행하므로, 코드에서 상태를 확인하지 않으면 catch도 실행되지 않고 오류도 보이지 않습니다.

코드가 response.ok나 response.status를 확인하는지 살펴보고, Network 패널에서 요청한 URL과 응답 본문이 기대한 것과 같은지 비교합니다. 주소가 틀렸는지, 서버의 경로가 바뀌었는지를 나눠 보면 원인을 좁힐 수 있습니다.

다음 글 성능 프로파일링에서는 Performance 패널과 Lighthouse로 페이지가 느린 원인을 측정하고, 측정 결과를 바탕으로 개선하는 방법을 살펴봅니다.