디버깅을 위한 React DevTools 가이드 대표 이미지

디버깅을 위한 React DevTools 가이드

2026. 1. 31.

React DevTools의 역사는 생각보다 꽤 되었다. npm에 배포된 1.0.0 버전을 기준으로 보니 9년이 되었다. 그런데 이 디버깅 도구에 대한 얘기가 오가는 것을 커뮤니티에서 거의 본 적이 없다.

공식 문서도 미흡하다. React 팀의 문서는 원래부터 아쉬움이 있어 왔지만, 메타가 직접 만든 공식 디버깅 도구에 대한 기본적인 가이드가 부족한 것은 아쉬운 부분이다. Developer Relations Engineer나 Technical Writer 같은 직무가 없는 것도 아닐 텐데, 메타 내에서 React 팀에 대한 리소스 배분 우선순위가 낮아 보인다. 최근 설립한 React Foundation이 어느 정도 역할을 할지 모르겠지만, 그룹에 속한 타 기업들이 힘을 써 주었으면 하는 마음이다.

본론으로 돌아가서, 그동안 React DevTools에도 많은 변화가 있었던 것으로 보인다. 사용법도 차근차근 다시 살펴볼 겸 React DevTools에서 할 수 있는 디버깅 작업을 정리해 보려고 한다. 사용될 예제는 이 글을 작성하는 데 활용하기 위해 AI 코딩만으로 구현한 대시보드이며, Next.js 16 App Router와 React 19.2 기준으로 작성되었다. 리팩터링을 하지 않았기 때문에 최적화할 여지가 다분해서 디버깅 도구를 살펴보는 데는 오히려 좋을 것이다.

React DevTools Chrome Web Store

Components

React DevTools에서 Components 탭은 React로 개발해 오던 분들이라면 가장 익숙할 도구다. 좌측 패널에서는 React Fiber 트리를 바탕으로 한 컴포넌트 호출 순서를 확인할 수 있다. 또한, 서버 컴포넌트와 메모이제이션 여부를 컴포넌트 이름의 우측 라벨로 확인할 수 있다.

우측 패널은 트리에서 선택한 컴포넌트의 상세 정보를 살펴볼 수 있다. 하나씩 살펴보자.

props

선택한 컴포넌트의 Props 정보를 확인할 수 있다.

  • 임의로 Prop 값을 제거해볼 수 있다. Prop이 제거된 상태로 리렌더링이 발생한다.
  • boolean 타입의 값은 디버깅용 토글 기능을 제공한다.
  • new entry는 디버깅을 위해 임의로 새로운 Prop을 추가하는 입력칸이다.
    • Chrome DevTools의 Elements에서 CSS 요소를 수정하듯 사용할 수 있다.

hooks

선택한 컴포넌트 내에서 실행된 Hook의 정보를 확인할 수 있다. 커스텀 Hook의 경우 useState와 같은 React의 빌트인 Hook이 사용되었는지 여부로 판별하여 감지한다.

  • use 접두사는 제외된 상태로 표기된다.
    • 예를 들어 useScrollLockScrollLock으로 나타난다.
    • 커스텀 Hook 이름은 use가 아니어도 인식한다.
  • Pathnamenext/navigation에서 제공하는 usePathname Hook이다.
    • hooks 정보만으로도 서드파티 커스텀 Hook의 내부 구현을 일부 엿볼 수 있다.
  • Context API 사용 여부도 확인할 수 있다.

state

선택한 컴포넌트에 존재하는 상태 값을 나타내는데, 다만 클래스형 컴포넌트만 포함된다. 함수형 컴포넌트에서는 useState와 같은 Hook을 사용하게 되는데, 이는 hooks에 표기되고 state에 별도로 표기되지 않는다.

suspended by

선택한 컴포넌트에 대해 Suspended 상태를 트리거하는 요소들이다. 개인적으로는 Components 탭에서 제공하는 핵심 정보 중 하나라고 생각한다.

  • suspended by에 항목이 있다면, 해당 컴포넌트는 React에서 Suspense로 감싸져 Fallback UI를 볼 수 있다는 의미다.
  • 트리거하는 원인은 scriptsrsc stream, dynamic, *(SSR)이 있다.
  • 괄호에 SSR이 붙은 것은 서버 측에서 이미 계산되어 넘어온 데이터를 의미한다.

코드상으로 어떤 요소가 Suspended 상태를 유발하는지 간혹 헷갈릴 때가 있다. 이때 suspended by로 확실하게 검증할 수 있다. 다양한 환경적 요인으로 Suspense로 감싸도 Fallback UI를 보지 못하는 경우가 있는데, 이는 네트워크 속도를 Slow로 줄여서 테스트해보거나, 추후 서술할 Suspended 상태로 강제 변경하는 기능을 이용하면 직관적으로 확인해 볼 수 있다.

JavaScript
(globalThis.TURBOPACK || (globalThis.TURBOPACK = [])).push([
  typeof document === 'object' ? document.currentScript : undefined,
  '[project]/packages/admin/src/shared/ui/navigation/components/index.ts [app-client] (ecmascript, next/dynamic entry, async loader)',
  (__turbopack_context__) => {
    __turbopack_context__.v((parentImport) => {
      return Promise.all(
        [
          'static/chunks/packages_admin_src_shared_ui_navigation_components_index_ts_bcf3217b._.js',
          'static/chunks/packages_admin_src_shared_ui_navigation_components_index_ts_cce9db13._.js',
        ].map((chunk) => __turbopack_context__.l(chunk)),
      ).then(() => {
        return parentImport(
          '[project]/packages/admin/src/shared/ui/navigation/components/index.ts [app-client] (ecmascript, next/dynamic entry)',
        );
      });
    });
  },
]);

위 이미지의 awaited value에 나타난 스크립트를 클릭하면 청크 단위로 분할된 JavaScript 코드를 확인할 수 있다. 코드가 좀 복잡해도 Promise.all을 보면 Suspended 상태를 트리거하겠다는 것을 알 수 있다. 꼭 직접 코드 스플리팅하여 dynamic 키워드로 불러오지 않아도 Next.js는 자체적으로 코드 스플리팅을 진행하여 불러온다.

rendered by

선택한 컴포넌트의 호출 체인을 살펴볼 수 있다.

  • 위에서부터 아래로 호출한 컴포넌트 순서를 나타낸다.
  • children 패턴으로 부모 컴포넌트에게서 전달받은 것을 단순히 렌더링하는 컴포넌트는 제외된다.
  • 좌측 패널에 나타나는 Fiber 트리와는 다르게 Context.Provider, Suspense, Fragment 등의 유틸리티 및 빌트인 컴포넌트는 제외된다.

source

선택한 컴포넌트가 사용된 실제 파일 이름과 코드 라인을 나타낸다.

  • Source Map 파일이 있어야 정확한 출처를 확인할 수 있다.
  • 서버 컴포넌트의 경우 출처를 알기 어렵다.

서버 컴포넌트의 내용은 이미 RSC로 직렬화된 상태로 받기 때문에 출처가 만약 있더라도, 눌러보면 아래와 같은 이해하기 어려운 문자열을 반환한다.

Text
({"processTicksAndRejections":_=>_()})
/* This module was rendered by a Server Component. Turn on Source Maps to see the server source. */
//# sourceURL=about://React/Server/unknown?43
//# sourceMappingURL=http://localhost:3000/__nextjs_source-map?filename=unknown

현재 기준으로 프로덕션 빌드에서는 next.config.js에서 productionBrowserSourceMaps 옵션을 활성화해 주어야 Source Map 파일이 생성된다. 다만, Source Map이 존재하더라도 이를 React DevTools에서 인식하지 못하는 이슈가 있다. 그래서 Source Map이 중요할 경우 개발 빌드에서 확인할 수밖에 없다.

Toolbar

Toolbar는 Components 탭에서 킬러 기능이 두 가지나 있는 중요한 패널이다. 우측 상단에 작은 버튼들로 존재한다. 좌측부터 차례로 살펴보자.

코드를 무언가 감싸는 모양의 버튼은 선택한 컴포넌트 파일을 코드 에디터로 여는 기능이다.

  • Chrome에서 로컬 네트워크 권한이 허용되어야 하며, 이는 기능을 사용할 때 물어본다.
  • 톱니바퀴 모양의 설정에서 실행할 코드 에디터를 선택할 수 있다.

느낌표 모양의 버튼은 선택한 컴포넌트를 Error 상태로 변경하는 기능이다. 킬러 기능 중 하나다.

  • Error 상태로 강제 변경하여 ErrorBoundary 컴포넌트 동작을 디버깅할 수 있다.
  • 가장 가까운 상위 ErrorBoundary의 Fallback UI를 확인할 수 있다.

버튼을 눌러서 Error를 강제로 발생시킨 상태다. state의 에러 메시지를 보면 DevTools로부터 시뮬레이션된 에러임을 알 수 있다. 또한 개발자 도구 밖의 실제 화면상에서도 에러 상태의 화면이 반영되므로 시각적으로 확인해가며 디버깅할 수 있다.

이어서 초시계 모양의 버튼은 선택한 컴포넌트를 Suspended 상태로 변경하는 기능이다. 이 또한 앞서 언급한 것처럼 킬러 기능 중 하나다.

  • Suspended 상태로 강제 변경하여 Suspense 컴포넌트 동작을 디버깅할 수 있다.
  • 가장 가까운 상위 Suspense의 Fallback UI를 확인할 수 있다.

마찬가지로 버튼을 눌러 Suspended 상태로 진입한 모습이다. children으로는 Lazy 컴포넌트를 호출한 모습이고 Fallback UI에 대한 정보도 보인다. 개발자 도구 밖의 실제 화면에서도 Fallback UI가 반영된 것을 확인할 수 있다.

눈 모양의 버튼은 선택한 컴포넌트에 대응하는 DOM 요소를 Chrome DevTools의 Elements 탭에서 확인하는 기능이다. 벌레 모양의 버튼은 선택한 컴포넌트의 상세 정보를 Chrome DevTools의 Console에 출력하는 기능이다. 마지막으로 코드 모양의 버튼은 선택한 컴포넌트를 Chrome DevTools의 Sources 탭에서 확인하는 기능이다. 당연하지만 source에서 정확한 출처가 있어야 유의미한 확인이 가능하다.

Profiler

Profiler 탭은 React 렌더링에서 Commit 단계가 발생했을 때, 어떤 컴포넌트가 렌더링되었고 시간이 얼마나 소요되었는지 시각화한 플레임 그래프 도구다.

기본적으로 Chrome DevTools의 Performance 탭과 사용 방법은 동일하다. 좌측 상단의 녹화 버튼을 눌러 원하는 사용자 시나리오에 맞춰 스냅샷을 기록한다.

좌측 상단을 보면 Flamegraph와 Ranked, Timeline이 있고 우측에는 톱니바퀴 모양의 설정 버튼과 세로 막대 그래프가 있다. 우선 세로 막대 그래프부터 살펴보자.

Snapshot Commit List

세로 막대 그래프는 스냅샷에 기록된 Commit들을 발생 순서대로 선택하여 볼 수 있다. 이 기능의 이름이 화면 상에는 정해져 있지는 않은데 코드를 찾아보니 SnapshotCommitList라는 이름의 컴포넌트로 관리되고 있었다.

https://github.com/facebook/react/blob/main/packages/shared/src/devtools/views/Profiler/SnapshotCommitList.js#L45-L71

그래프가 나타내는 색상의 의미는 아래와 같다.

  • 노란색은 렌더링 시간이 오래 걸린 커밋이다.
  • 청록색은 렌더링 시간이 짧았던 커밋이다.

막대를 클릭하면 하단의 플레임 그래프가 바뀌면서 선택한 Commit 단계에 맞춰 보여준다. 그래프 좌측의 톱니바퀴 버튼을 눌러서 Commit에 소요된 시간에 따라서 필터링할 수 있는 기능도 제공한다.

일반적으로 1프레임에 16ms 이하로 소요되어야 사용자 입장에서 불편함이 없다. 다만, 16ms에는 JavaScript의 작업만 포함되는 것이 아닐뿐더러 React는 JavaScript 안에서도 부분적인 영역이다. 그러므로 Commit의 소요 시간을 적게는 1ms 이하로 유지하면 정말 좋고, 많게는 3~4ms 이하로 생각하는 것이 좋을 듯하다.

Commit 타임라인의 막대를 클릭하면 해당 Commit 단계의 전반적인 정보를 우측 패널에서 확인할 수 있다.

Priority는 React 18 이후 도입된 동시성 스케줄러가 Task에 할당하는 우선순위를 나타낸다.

  • Immediate는 클릭, 입력 등 즉각적인 반응이 필요한 동기적 업데이트다.
  • User-blocking은 사용자 인터랙션 결과로 발생하며, 화면이 빨리 바뀌어야 하는 업데이트다.
  • Normal은 데이터 로딩, 사이드 이펙트 등 일반적인 상태 업데이트다. 급박한 인터랙션은 아니므로 일반적인 우선순위로 처리된다.
  • Low는 조금 늦게 처리되어도 사용자 경험에 큰 지장이 없는 백그라운드 작업이다.
  • Idle은 브라우저가 아무 일도 하지 않을 때 처리해도 되는 가장 낮은 순위다.
  • useTransition 등 동시성 관련 API를 사용할 때 우선순위를 확인하면 의도한 대로 동작하는지 검증할 수 있다.

Priority를 통해 React의 동시성 스케줄링이 의도대로 작동하는지 확인할 수 있다. 예를 들어, startTransition()으로 감싼 업데이트는 낮은 우선순위로 처리되어야 하는데, 만약 높은 우선순위로 나타난다면 의도와 다르게 동작하고 있는 것이다.

  • Committed at은 스냅샷 녹화가 시작된 후 해당 Commit 단계가 완료되기까지 걸린 시간이다.
  • Render는 렌더링에 걸린 시간이다.
  • Layout effectsuseLayoutEffect에 소요된 시간이다.
  • Passive effectsuseEffect에 소요된 시간이다.
    • 브라우저 페인팅 이후 비동기적으로 실행되므로 수동적(Passive)이라고 표현한 것으로 보인다.

간혹 What caused this update? 항목이 나타나는 경우가 있다.

  • 해당 Commit 단계를 발생시킨 직접적인 원인이 되는 컴포넌트 리스트다.
  • React는 여러 개의 상태 변경을 하나로 묶어 처리하는 Batching 작업을 하므로 Commit 단계에 여러 컴포넌트가 원인으로 지목될 수 있다.
  • 어떤 컴포넌트의 렌더링 원인이 아니라, 해당 Commit 단계 발생의 원인이라는 점에 유의해야 한다.

Flamegraph

Flamegraph는 선택한 Commit의 렌더링 정보를 플레임 그래프로 시각화하여 보여준다. Chrome DevTools의 Performance 탭을 사용해 봤다면 익숙한 형태일 것이다. 좌측의 플레임 그래프와 우측의 상세 정보 패널로 구성되어 있다.

플레임 그래프에서 각 컴포넌트는 색상으로 상태를 구분할 수 있다.

  • 노란색은 렌더링 시간이 상대적으로 오래 걸린 컴포넌트다.
  • 청록색은 렌더링 시간이 짧았던 컴포넌트다.
  • 회색은 이번 Commit에서 렌더링되지 않은 컴포넌트다.
  • 가로축 길이는 해당 컴포넌트와 자식들을 렌더링하는 데 걸린 총 시간을 의미한다.
  • 반짝이는 이모지가 있는 컴포넌트는 React Compiler에 의해 자동으로 메모이제이션된 컴포넌트다.

회색 컴포넌트는 메모이제이션이나 조건부 렌더링으로 인해 리렌더링을 건너뛴 경우다. 성능 최적화가 잘 된 애플리케이션에서는 회색 컴포넌트 비율이 높게 나타난다.

반짝이는 이모지는 memo로 직접 메모이제이션한 컴포넌트는 이모지가 표기되지 않는다. React Compiler가 비활성화된 상태여도 마찬가지다.

플레임 그래프에서는 특정 컴포넌트를 선택하면 우측 패널에서 해당 컴포넌트의 상세 정보를 확인할 수 있다.

Why did this render?는 말 그대로 렌더링 발생 원인을 나타낸다. 이미지 기준으로는 kpis라는 Props로 인해 렌더링 되었다고 나타내고 있다. Why did this render?에서 나타나는 원인 종류는 아래와 같다.

  • This is the first time the component rendered.
  • Context changed: [Key 이름]
  • Hooks changed 혹은 구체적인 Hook 이름
  • Props changed: [Prop 이름]
  • State changed: [State 이름]
  • The parent component rendered.

Rendered at은 선택한 컴포넌트가 스냅샷 안에서 몇 번 렌더링되었는지를 나타낸다.

  • 각 렌더링 항목은 해당 Commit 단계에서 얼마나 렌더링 시간을 차지하는지 나타낸다.
  • 각 렌더링 항목을 클릭하면 대응하는 Commit 단계로 이동한다.

이미지 기준으로 하나의 Commit에서 네 번 렌더링되었다는 의미가 아니므로 오해하지 말자. 다만, 스냅샷 촬영 시 새로고침이나 버튼 클릭 같은 사용자 시나리오를 하나를 기록했는데 네 번 컴포넌트 렌더링이 발생했다면, 의도한 것이 아니라면 불필요한 리렌더링이 발생한 것이므로 원인을 찾는 것이 좋을 것이다.

Ranked

Ranked는 선택한 Commit에서 렌더링 시간이 오래 걸린 컴포넌트 순으로 정렬하여 가로 막대 그래프를 보여준다. Flamegraph에서는 컴포넌트 계층 구조를 따라가며 문제를 찾아야 하지만, Ranked를 사용하면 렌더링 병목 지점을 직관적으로 파악할 수 있다. 성능 최적화를 시작할 때 어떤 컴포넌트부터 개선해야 할지 우선순위를 빠르게 정하는 데 유용할 수 있겠다.

Ranked 차트를 볼 때는 상위 2~3개 컴포넌트에 집중하는 것이 효율적이다. 보통 전체 렌더링 시간의 대부분은 소수의 컴포넌트에서 발생하기 때문이다. 해당 컴포넌트들을 클릭하여 "Why did this render?"를 확인하면 최적화 방향을 대략 파악할 수 있다.

Timeline

현재 Next.js 16과 React 19.2 버전에서는 "This current profile does not contain timeline data." 메시지와 함께 타임라인이 보이지 않는다. 아마 의도한 것은 아니겠으나 Next.js 15 버전으로 내려가면 "React 19.2 이상에서는 대신 React Performance tracks를 사용할 수 있습니다." 라는 또 다른 문구가 나온다.

2025년 1분기 즈음부터 Chrome DevTools의 Performance 탭에서 Performance API를 활용한 React 컴포넌트 관련 디버깅 정보를 지원하기 시작했는데, 이것과 역할이 겹쳐서 Timeline 기능을 지원 중단하려는 움직임이 아닐까 싶다. 실제로 관련 커밋을 발견했다.

https://github.com/facebook/react/commit/bc828bf6e3b762747134099f338a2b57df586f43

Suspense

Suspense 탭은 2025년 10월 메이저 업데이트(v7.0)를 통해 도입된 실험적 기능이다. 내부 코드상으로는 React 19.3.0-canary 이상부터 지원하도록 되어있는데, 이유는 모르겠으나 Next.js 16.1과 React 19.2에서도 지원한다. 참고로 Next.js의 내부 React 버전도 19.2였다. Next.js 15 + React 19.2 버전에서는 Suspense 탭이 보이지 않았다.

https://github.com/facebook/react/blob/main/packages/shared/src/backend/agent.js#L964-L972

기본적으로 이 탭은 Suspended 상태를 시각적으로 볼 수 있는 디버깅 도구다. Chrome DevTools의 Layers 도구와 유사한 형태로, Suspended 상태를 레이어로 시각화하여 컴포넌트 경계를 직관적으로 파악할 수 있다. 스트리밍 SSR이나 복잡한 비동기 로딩 구조에서 Suspended 상태만 집중적으로 볼 수 있다는 점이 꽤 유용하다.

하단에는 Suspended 상태가 발생한 순서대로 타임라인을 제공한다.

  • 플레이백 기능으로 각 Suspended 상태의 스냅샷을 시간 순서대로 확인할 수 있다.
  • 과거 상태부터 차례로 클릭하여 당시 화면에 반영되었던 상태를 재현해 볼 수 있다.
  • 특정 시점의 스냅샷을 클릭해 당시의 UI 로딩 상태를 재현해 볼 수 있다는 점은 스트리밍 SSR 디버깅에서 강력한 기능 중 하나다.

Next.js App Router의 스트리밍 SSR을 사용하면 서버에서 준비된 순서대로 컴포넌트가 클라이언트로 전송되는데, 이때 어떤 순서로 Suspended 상태가 해제되는지 타임라인으로 명확하게 확인할 수 있다. 의도한 로딩 순서와 다르게 동작한다면 Suspense 경계를 조정하거나 데이터 요청 순서를 재검토할 수 있다.

우측 패널에는 Components 탭과 동일한 정보를 제공하는데, 선택한 Suspense 컴포넌트의 상세 정보를 확인할 수 있다.

개인적으로 Suspense 탭은 향후 React 버전 지원이 늘어나면, 스트리밍 SSR 환경에서 꽤 유용하게 활용할 수 있을 것으로 보인다. 쓸 만하게 만들어 놓고 React DevTools의 공식 문서에 Suspense 탭에 대한 언급이 전혀 없다는 점은 굉장히 의아하다.

마치며

React DevTools는 React 애플리케이션 개발에서 꽤 유용한 도구다. Components 탭으로 컴포넌트 구조와 상태를 파악하고, Profiler로 성능 병목을 찾아 개선할 수 있다. Suspense 탭은 비교적 최신 기능이지만 스트리밍 SSR 환경에서 유용하게 활용할 수 있다.

개발 단계에서는 Components 탭을 주로 활용하면서, Suspense 탭도 많은 버전에서 지원되면 활용하기에 좋아 보인다. Toolbar의 Suspended 시뮬레이션으로 Suspense Fallback UI를 확인하고, Error 시뮬레이션으로 ErrorBoundary 동작을 검증한다. 이 과정에서 적절한 SuspenseErrorBoundary 경계를 한 번 더 고민하게 될 텐데, 이것만으로도 사용자 경험 개선에 효과적이다.

성능 최적화 단계에서는 사용자 시나리오를 먼저 정리한 후 시나리오별로 Profiler로 측정한다. 그리고 Flamegraph에서 노란색으로 표시되는 컴포넌트 위주로 빠르게 병목을 찾는다. Ranked 차트로 영향도가 큰 상위 2~3개 컴포넌트만 집중해도 체감 성능이 눈에 띄게 개선되는 경우가 많을 것이다.

React DevTools의 공식 문서가 부족한 것은 여전히 아쉽지만, 도구 자체는 많이 좋아졌다. 이 글이 React DevTools를 더 깊이 이해하고 활용하는 데 도움이 되었으면 한다.

References