React Native 디버깅 완벽 가이드 2026: DevTools, Hermes, Reactotron 실전

Flipper가 빠진 2026년 React Native에서 DevTools, Hermes, Reactotron으로 디버깅하는 실전 워크플로우. 콘솔, 중단점, 네트워크, 프로파일러, 크래시 소스맵까지 한 번에 정리.

React Native 디버깅 가이드 2026 (Hermes)

업데이트: 2026년 6월 20일

React Native 디버깅의 2026년 표준은 React Native DevTools(Chrome DevTools 프로토콜 기반)와 Hermes 디버거를 함께 쓰는 것이다. Flipper는 React Native 0.74에서 기본 통합이 제거됐고, 0.76부터는 j 키 하나로 DevTools가 바로 열린다. 솔직히 말하면, 처음 Flipper가 사라진다고 했을 때 나도 좀 막막했다. 그런데 막상 써보니 웹 Chrome DevTools를 쓰던 경험이 그대로 이어져서, 처음 모바일로 넘어온 React 개발자라면 학습 곡선이 거의 없다. 이 글에서는 콘솔, 네트워크, 컴포넌트 트리, 성능, 메모리, 크래시 분석까지 실제 프로덕션에서 쓰는 워크플로우를 한 번에 정리한다.

  • Flipper는 RN 0.74(2024-04)에서 기본 제거됐고, 0.76부터 React Native DevTools가 기본 디버거다. Chrome DevTools 프론트엔드를 그대로 사용한다.
  • Metro에서 j를 누르면 DevTools가 열리고, Console·Sources(중단점)·React Components·React Profiler·Network·Memory 패널을 사용할 수 있다.
  • Hermes는 기본 엔진이며 CDP(Chrome DevTools Protocol)를 직접 구현한다. JSC와 달리 원격 JS 디버깅 없이 온디바이스에서 중단점이 동작한다.
  • Reactotron은 상태 변경·API 호출·커스텀 명령을 시간순으로 추적하는 데 여전히 유용하다. DevTools와 역할이 겹치지 않는다.
  • 프로덕션 크래시는 Hermes 바이트코드 스택을 소스맵으로 역변환해야 의미가 생긴다. EAS Build는 sentry-expo 또는 @sentry/react-native로 자동 업로드한다.
  • 네이티브 크래시는 JS 디버거로 보이지 않는다. Xcode Console.app과 Android Studio Logcat을 함께 봐야 한다.

Flipper는 왜 사라졌고 무엇으로 대체됐나

Flipper는 2018년부터 Meta가 만든 데스크탑 디버거였지만, 유지보수 비용과 New Architecture 호환성 문제로 React Native 0.74(2024년 4월)에서 기본 통합이 제거됐다. 공식 블로그에서 Meta는 "Flipper 플러그인 생태계를 유지하느니, 이미 모든 웹 개발자가 알고 있는 Chrome DevTools를 쓰는 게 낫다"는 결론을 밝혔다. 그 결과물이 React Native DevTools다. 정확히는 Hermes가 구현한 CDP(Chrome DevTools Protocol) 위에 Chrome DevTools 프론트엔드를 그대로 띄운 것이다.

이 결정이 웹 출신 개발자에게 주는 의미는 크다. console.log, debugger;, 중단점, Watch 표현식, 콜 스택, 스코프 변수 검사처럼 웹에서 쓰던 키 단축과 워크플로우가 그대로 동작한다. 자세한 변경 사항은 React Native 0.74 릴리스 노트에서 확인할 수 있다. Redux DevTools 같은 일부 Flipper 플러그인은 별도의 npm 패키지나 Reactotron으로 대체해야 한다.

여전히 Flipper를 쓰고 싶다면 0.73 이하 버전에 묶이게 되고, RN 0.76부터 본격화된 New Architecture(Fabric·TurboModules)와 호환성 이슈가 누적된다. 이미 0.73은 보안 패치 외의 업데이트가 끊겼으므로, 신규 프로젝트라면 처음부터 DevTools를 기준으로 설정하는 것을 권장한다. 자세한 New Architecture 마이그레이션은 Turbo Modules 완벽 가이드를 참고하자.

React Native DevTools 사용법

자, 그럼 본격적으로 들어가보자. RN 0.76 이상에서 DevTools를 여는 방법은 단순하다. Metro 번들러가 띄워진 터미널에 포커스를 두고 j 키를 누르면 된다. 시뮬레이터·실기기·여러 디바이스가 동시에 연결돼 있어도 Metro가 자동으로 인식해 단일 DevTools 창에 디바이스 선택 드롭다운을 띄운다. 단축키가 막힐 때는 디바이스의 Dev Menu(시뮬레이터 Cmd+D / 실기기 흔들기)에서 Open DevTools를 선택해도 동일하다.

핵심 패널은 다섯 개다.

  • Console: console.log/warn/error, 객체 검사, REPL 평가. 웹과 거의 동일하다.
  • Sources: 원본 TS/JS에 중단점을 찍을 수 있다. Hermes가 인라인 소스맵을 자동으로 노출하므로 별도 설정이 필요 없다.
  • React Components: 컴포넌트 트리, props/state, 훅 값 검사.
  • React Profiler: 렌더 횟수와 커밋 단위 비용 측정.
  • Network: fetch/XHR 요청 헤더·바디·타이밍.

웹 Chrome DevTools와 다른 한 가지는 "Preserve log"가 기본 OFF라는 점이다. 핫 리로드 시 Console이 깨끗하게 초기화되어 디버깅 흐름이 끊기곤 한다. 패널 우측 상단 톱니바퀴에서 켜두자(나는 처음 며칠 이걸 모르고 로그가 자꾸 사라진다고 고생했다).

// App.tsx: 중단점 디버깅 예시
import { useEffect, useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 이 줄에 중단점을 찍거나, 코드에 직접 debugger; 를 적어도 된다.
    debugger;
    console.log('mounted with', { count });
  }, []);

  return null;
}

Hermes 엔진에서 중단점 디버깅하기

Hermes는 RN 0.70부터 기본 엔진이고, 2026년 기준 사실상 모든 신규 프로젝트가 Hermes 위에서 돈다. 과거 JSC 시대의 "원격 JS 디버깅"(Chrome에 V8 컨텍스트를 띄우고 WebSocket으로 JS를 보내는 방식)은 시간차·메모리 차이·네이티브 모듈 호환성 문제로 악명 높았다. Hermes는 이를 버리고 CDP를 엔진 내부에 구현했다. 결과적으로 중단점이 디바이스 위에서 실행되는 코드에 직접 걸린다. 타이머, 네이티브 콜백, requestAnimationFrame 모두 정확한 위치에서 멈춘다.

실무에서 자주 만나는 함정 두 가지가 있다. 첫째, release 빌드에서는 인라인 소스맵이 제거되므로 DevTools 디버거가 의미 있는 줄 번호를 보여주지 못한다. 디버깅이 필요하면 항상 dev 빌드(--mode Debug 또는 npx expo start)로 돌리자. 둘째, Hermes 바이트코드 캐시가 오래된 코드를 잡고 있을 때가 있다. 이게 진짜 짜증나는 부분인데, npx react-native start --reset-cache 혹은 Expo 사용자라면 npx expo start -c로 캐시를 초기화하면 된다. 나도 이전 프로젝트에서 "왜 수정한 코드가 안 먹지?" 하면서 30분을 날렸던 적이 있다.

Hermes의 디버깅 기능은 New Architecture 공식 문서에 따라 Fabric 렌더러 활성화 여부와 무관하게 동일하게 동작한다. 한 가지 더, console.log는 dev 빌드에서만 호출 직후 Metro 콘솔로 전송된다. release에서 로그를 보고 싶다면 Sentry.captureMessage 같은 외부 채널을 사용해야 한다.

네트워크 요청 검사하기

RN 0.76부터 DevTools의 Network 패널이 정식 지원된다. fetch·XHR·axios가 모두 추적되며, 요청/응답 헤더, JSON 바디, 타이밍 워터폴까지 웹과 동일하게 표시된다. 응답 바디는 최대 10MB까지 캐싱하므로 큰 이미지·동영상 스트림 디버깅에도 충분하다. WebSocket 메시지 흐름도 별도 탭에서 시간순으로 보인다.

다만 HTTP/2와 gRPC-Web은 아직 헤더 디코딩이 불완전하다. 백엔드와 바이너리 프로토콜로 통신한다면 Charles Proxy나 Proxyman을 함께 쓰는 편이 안전하다. iOS 시뮬레이터에서는 Settings → Wi-Fi → HTTP Proxy → Manual로 프록시 주소(보통 127.0.0.1:8888)를 설정한다. Android 에뮬레이터는 adb shell settings put global http_proxy 10.0.2.2:8888 한 줄로 끝난다.

React Query/TanStack Query를 쓴다면 TanStack Query DevTools의 React Native 어댑터(@tanstack/react-query-devtools-rn)도 함께 켜자. 캐시 키, stale/fresh 상태, refetch 트리거가 한 화면에 나와 네트워크 패널과 상호 참조하기 좋다. API 응답 캐시 전략과 상태 정규화는 React Native 상태 관리 가이드에서 더 자세히 다룬다.

React DevTools로 컴포넌트 트리 보기

React DevTools는 DevTools 내부의 Components 탭으로 통합됐지만, 더 큰 화면이 필요하면 독립 실행도 가능하다. npx react-devtools를 실행하면 별도 Electron 창이 뜨고, Metro가 그쪽으로도 트래픽을 보내준다. 트리에서 컴포넌트를 클릭하면 props·state·hooks가 우측 패널에 나오고, 훅은 호출 순서대로 번호가 매겨진다. useState·useReducer 값을 더블클릭으로 즉시 수정해 UI 반응을 테스트할 수 있다.

웹에서 자주 쓰던 "Highlight updates when components render" 옵션도 그대로 있다. 불필요한 리렌더가 의심되면 켜두자. 깜빡임이 잦으면 React.memo, useCallback, useMemo의 의존성 배열을 점검하거나 상태를 더 아래로 내려야 한다는 신호다. New Architecture에서 Fabric은 리렌더 비용을 크게 낮췄지만, 그렇다고 매 키 입력마다 화면 전체를 리렌더하는 패턴이 공짜가 되는 것은 아니다.

한 가지 자주 묻는 게 있는데, 웹에서 익숙한 data-testid 같은 식별자가 트리에 보이지 않는다는 점이다. RN에서는 testID 속성이지만 Components 패널은 React 컴포넌트 이름을 기준으로 표시하므로, displayName을 설정해두면 익명 컴포넌트나 HOC 트리에서 길을 잃지 않는다.

React Profiler로 성능 분석하기

Profiler 탭은 React가 각 커밋에서 무엇을 얼마나 렌더했는지 보여준다. 녹화 버튼을 누르고 실제 인터랙션을 한 뒤 멈추면, 커밋별로 컴포넌트 트리가 플레임그래프로 그려진다. 노란색·빨간색 막대는 렌더 시간이 긴 컴포넌트다. 작은 단축은 다음 세 가지 신호를 먼저 보는 것이다.

  1. 같은 컴포넌트가 인터랙션마다 노란색이라면 props가 새로 생성되는지(style={{}}, 인라인 함수) 확인.
  2. FlatList의 모든 자식이 매번 렌더되면 keyExtractorrenderItem을 메모화하고, getItemLayout으로 측정 비용을 제거.
  3. 한 커밋의 시간이 16ms를 넘어가면 60fps를 못 맞춘다. 무거운 작업은 InteractionManager.runAfterInteractions 또는 Reanimated 워클릿으로 옮긴다.

JS 스레드가 아닌 네이티브/UI 스레드 측정이 필요하면 Xcode Instruments(Time Profiler·Allocations)나 Android Studio의 CPU Profiler를 사용한다. 셰이더·애니메이션 비용은 Skia·Reanimated 가이드에서 다룬 GPU 디버그 옵션을, 메모리 누수와 렌더링 최적화는 React Native 성능 최적화 가이드에서 더 자세히 다뤘다.

Reactotron 설정과 실전 활용

DevTools가 모든 걸 다 해주지는 않는다. Reactotron은 데스크탑 앱에 이벤트 타임라인을 그려주는 도구로, 상태 변경·API 호출·AsyncStorage 변경·커스텀 명령을 시간순으로 한눈에 본다. 사실 내 경우엔, Redux/Zustand 액션 디스패치를 뒤로 감으면서 "어디서 상태가 이상해졌지?"를 추적할 때 DevTools보다 훨씬 빠르다.

설치는 두 단계다.

npm install --save-dev reactotron-react-native reactotron-redux
// ReactotronConfig.ts
import Reactotron from 'reactotron-react-native';
import { reactotronRedux } from 'reactotron-redux';

if (__DEV__) {
  Reactotron
    .configure({ name: 'MyApp', host: 'localhost' })
    .useReactNative({ networking: true, asyncStorage: true })
    .use(reactotronRedux())
    .connect();

  // 글로벌에 노출하면 콘솔에서 직접 호출 가능
  (console as any).tron = Reactotron;
}

이후 console.tron.log('checkpoint', payload)로 표준 로그와 분리된 채널에 출력할 수 있다. 가장 유용한 기능은 커스텀 커맨드다. Reactotron 데스크탑에서 버튼을 눌러 앱 내부 함수를 호출하게 만들면, QA 단축키·테스트 데이터 시드·기능 플래그 토글을 별도 UI 없이 즉시 트리거할 수 있다. 실기기에서는 host를 개발 머신의 LAN IP로 바꿔야 연결된다.

LogBox·RedBox로 런타임 경고 관리

LogBox는 console.warn을 화면 하단 노란 배너로, console.error를 빨간 RedBox로 띄우는 RN 내장 위젯이다. 외부 라이브러리에서 의도하지 않은 경고가 쏟아져 화면을 가리는 일이 잦은데, 무시하려면 진입점에 다음을 추가한다.

import { LogBox } from 'react-native';

LogBox.ignoreLogs([
  // 알고 있고 추후에 처리할 경고만 정확한 문자열로 적는다.
  'Non-serializable values were found in the navigation state',
  /VirtualizedLists should never be nested/, // 정규식도 가능
]);

절대로 LogBox.ignoreAllLogs()는 쓰지 말자. 진짜 위험한 에러까지 묻혀버린다. 운영에서 발견된 RedBox는 거의 항상 비동기 콜백 내 미잡힌 Promise 거부거나 잘못된 prop 타입이다. 전자는 전역 ErrorUtils.setGlobalHandler로 트래킹 SDK에 흘려보내고, 후자는 TypeScript strict 모드와 ESLint react/no-deprecated-api 규칙으로 빌드 단계에서 잡는 게 비용이 가장 낮다.

웹의 window.onerror·unhandledrejection에 익숙하다면, RN에서는 ErrorUtils.setGlobalHandler((error, isFatal) => { ... })로 같은 일을 한다. Hermes에서는 미잡힌 Promise도 이 핸들러로 들어온다.

소스맵과 프로덕션 크래시 디버깅

Hermes는 JS를 바이트코드로 컴파일해 번들에 싣는다. release 빌드의 크래시 스택에는 0x1f3a 같은 바이트코드 오프셋이 찍히고, 사람이 읽을 줄 번호가 없다. 솔직히 소스맵 없이는 디버깅이 사실상 불가능하다.

해결책은 빌드 시 소스맵을 생성하고 크래시 리포터에 업로드해두는 것이다. Sentry React Native 공식 가이드가 가장 정돈된 워크플로우를 보여준다. Expo + EAS Build 사용자라면 app.jsonplugins@sentry/react-native/expo를 추가하기만 하면 EAS가 빌드 산출물과 함께 소스맵·디버그 ID를 Sentry에 자동 업로드한다.

// metro.config.js: 소스맵 생성을 명시적으로 켜둔다
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);

config.transformer.minifierConfig = {
  // Hermes 바이트코드와 매핑되도록 keep_classnames/keep_fnames 유지
  keep_classnames: true,
  keep_fnames: true,
};

module.exports = config;

EAS 이외의 환경이라면 직접 업로드 명령을 실행한다. iOS는 npx react-native bundle --platform ios --dev false --entry-file index.js --bundle-output main.jsbundle --sourcemap-output main.jsbundle.map로 생성한 뒤 sentry-cli sourcemaps upload로 보낸다. 디버그 ID(--include-strict)를 함께 업로드하면 앱 버전과 무관하게 자동 매칭된다.

네이티브 크래시: Xcode와 Logcat

JS에서 잡히지 않는 크래시(TurboModule 초기화 실패, JNI 예외, 메모리 압박으로 인한 강제 종료 등)는 DevTools 콘솔에 흔적이 남지 않는다. iOS는 Xcode → Window → Devices and Simulators → View Device Logs에서 크래시 리포트를 받고, dSYM이 있다면 자동 심볼라이즈된다. Android는 adb logcat *:E로 에러 레벨만 추리는 것이 좋다. ReactNativeJS 태그를 보면 JS 측 예외도 함께 나온다.

웹 출신이 자주 혼동하는 부분 한 가지. iOS의 EXC_BAD_ACCESS는 거의 항상 네이티브 측 메모리 문제로, JS 스택이 도움이 안 된다. 최근 추가한 네이티브 모듈이나 JSI 직접 호출 지점을 의심하자. 나도 지난 프로젝트에서 이 에러가 떠서 며칠 JS 코드를 뒤지다가, 결국 네이티브 모듈의 dealloc 타이밍 이슈였던 경우가 있다. Hermes 자체 버그라면 GitHub 이슈로 보고하기 전에 nightly 빌드를 한 번 더 시도해본다.

마지막으로, CI에서 자동 회귀 테스트를 돌리고 있다면 디바이스 로그를 아티팩트로 저장해두자. Maestro E2E 테스트 실패 시 Logcat·시뮬레이터 로그가 함께 있어야 사후 분석이 가능하다.

자주 묻는 질문

React Native에서 Flipper는 더 이상 사용할 수 없나요?

RN 0.74부터 Flipper 기본 통합이 제거됐고, 0.76부터는 React Native DevTools가 기본 디버거다. 직접 의존성을 다시 추가하면 0.74에서도 동작하지만, Meta의 공식 권고는 DevTools로 이동하는 것이다. 0.76 이상에서는 New Architecture 비호환 이슈로 사실상 비현실적이다.

React Native DevTools와 Reactotron 중 무엇을 써야 하나요?

둘 다 쓰는 것이 정답이다. DevTools는 중단점·컴포넌트 트리·네트워크·성능 프로파일링을, Reactotron은 시간축 기반 상태 변경 추적과 커스텀 QA 명령을 잘한다. 역할이 겹치지 않으므로 서로 보완한다.

Hermes에서 console.log가 느린 이유는 무엇인가요?

dev 빌드의 console.log는 JS 스레드에서 Metro로 메시지를 직렬화해 전송한다. 큰 객체를 매 프레임 로깅하면 60fps가 깨질 수 있다. 루프 안에서는 로그를 빼고, 정말 필요하면 Reactotron.log로 별도 채널에 보내거나 release 빌드에서 자동 제거되도록 babel-plugin-transform-remove-console을 적용한다.

실기기에서 DevTools가 연결되지 않을 때 어떻게 해결하나요?

실기기가 Metro 머신과 같은 Wi-Fi에 있는지 확인하고, 회사 네트워크의 클라이언트 격리(client isolation)가 꺼져 있어야 한다. USB 연결이라면 iOS는 idevicepair pair, Android는 adb reverse tcp:8081 tcp:8081로 포트 포워딩을 강제한다. 그래도 안 되면 Metro를 --host 0.0.0.0으로 다시 띄운다.

프로덕션 크래시의 minified 스택을 어떻게 원본 코드로 변환하나요?

빌드 시 생성된 .map 파일을 Sentry·Crashlytics·Bugsnag 같은 리포터에 업로드해두면 자동으로 역변환된다. 수동 변환이라면 npx hermes-source-map-tool 또는 npx metro-symbolicate main.jsbundle.map 명령으로 스택 텍스트를 변환할 수 있다. 디버그 ID 기반 업로드가 가장 실수가 적다.

Anita Iyer
저자 소개 Anita Iyer

Cross-platform mobile developer who came to RN from web. Bridges the two worlds and explains the seams.