React Native CLI 환경에서 프로덕션 레벨의 앱을 준비할 때, 사용자가 앱을 쓰다 갑자기 앱이 픽 꺼져버리는(Crash) 현상만큼 서비스에 치명적인 것은 없다.
하지만 안타깝게도 모든 상황 100% 에러가 없이 완벽하게 작동이 되진 않는다. 어떠한 이유로든 앱이 에러가 터져 죽는 상황이 생길 수 있다. 그렇다면, 현실적으로 다 막기 어렵다면 에러가 발생했을 때 대비를 해놓는 것이 할수 있는 최선이다.
앱이 죽는 대신 안전하게 '안내 화면'을 띄워주고 에러를 수집할 수 있는 eact-native-error-boundary 기반의 최신 에러 핸들링 전략을 살펴보자.
React의 Error Boundary는 컴포넌트 트리 하위에서 발생하는 JavaScript 렌더링 에러를 중간에 가로채는 '안전망'이다.
기본적으로 React Native 앱에서 렌더링 중 에러가 발생하면 개발 모드에서는 무서운 '빨간 화면(Red Screen of Death)'이 뜨고, 프로덕션(배포) 환경에서는 앱이 강제로 종료(Crash)된다.**
react-native-error-boundary 라이브러리는 이 강제 종료를 막고, 에러를 잡아내어 우리가 만든 '커스텀 에러 화면(Fallback UI)'으로 부드럽게 전환해주는 역할을 한다.
pnpm install react-native-error-boundary
앱의 기획에따라, 다음과 같이 에러를 받아줄 구역을 구분할 수 있다.
// App.tsx
export default function App() {
// 에러가 발생했을 때 로깅 툴(Sentry, Crashlytics 등)로 전송하는 콜백
const handleError = (error: Error, stackTrace: string) => {
console.error('JS 에러 감지:', error);
// ex) Sentry.captureException(error);
};
return (
<ErrorBoundary
FallbackComponent={CustomErrorScreen}
onError={handleError}
>
<RootNavigator />
</ErrorBoundary>
);
}
// 구역별로 작게 보여줄 에러 UI
const WidgetFallback = ({ resetError }) => (
<View style={{ padding: 10, backgroundColor: '#fee', borderRadius: 8 }}>
<Text>정보를 불러오지 못했습니다.</Text>
<Button title="다시 시도" onPress={resetError} />
</View>
);
export default function DashboardScreen() {
return (
<View style={{ flex: 1 }}>
{/* 1. 상단 프로필 구역 (에러가 나도 여기만 Fallback으로 바뀜) */}
<ErrorBoundary FallbackComponent={WidgetFallback}>
<UserProfileWidget />
</ErrorBoundary>
{/* 2. 하단 리스트 구역 */}
<ErrorBoundary FallbackComponent={WidgetFallback}>
<RecommendationList />
</ErrorBoundary>
</View>
);
}
에러가 터졌을 때 유저에게 보여줄 화면을 만들어보자. 이 라이브러리의 가장 큰 장점은 resetError 라는 함수를 props 넘겨주어, 유저가 '다시 시도' 버튼을 눌러 앱을 껐다 켜지 않고도 화면을 복구할 수 있게 해준다는 점이다.
// src/components/CustomErrorScreen.tsx
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
// 라이브러리에서 제공하는 Props 타입
interface FallbackProps {
error: Error;
resetError: () => void;
}
export default function CustomErrorScreen({ error, resetError }: FallbackProps) {
return (
<View style={styles.container}>
<Text style={styles.title}>문제가 발생했습니다.</Text>
<Text style={styles.description}>
일시적인 오류가 발생했습니다. 아래 버튼을 눌러 다시 시도해 주세요.
</Text>
{/* 개발 환경에서만 실제 에러 메시지를 보여주어 디버깅을 도와주자 */}
{__DEV__ && (
<View style={styles.errorBox}>
<Text style={styles.errorText}>{error.message}</Text>
</View>
)}
<TouchableOpacity style={styles.button} onPress={resetError}>
<Text style={styles.buttonText}>다시 시도하기</Text>
</TouchableOpacity>
</View>
);
}
react-native-error-boundary는 만능이 아니다. 이 라이브러리는 아래의 에러들은 잡지 못하고 튕기게 둔다.
비동기 코드 에러
setTimeout, setInterval, requestAnimationFrame 내부의 에러.
API 통신 에러
fetch나 axios의 Promise Rejection. (TanStack Query를 쓴다면 Query 내부에서 핸들링해야 한다.) 반드시 try-catch로 감싸거나 전역 인터셉터(axios interceptor 등)를 활용한다.
이벤트 핸들러 에러
Error Boundary는 렌더링 과정에서 발생하는 에러만 잡아낸다. onPress나 setTimeout은 렌더링이 끝난 뒤 별도의 콜백 큐에서 실행되기 때문에, 여기서 터진 에러는 바운더리를 그냥 통과해 버린다. react-native-exception-handler 같은 라이브러리를 추가로 사용하여 비동기/전역 JS 에러를 잡으면 좋다.
네이티브 에러 (가장 치명적)
React(JS) 단이 아니라 Android(Java/Kotlin)나 iOS(Swift/Obj-C) 네이티브 모듈에서 발생한 크래시이며 RN에서는 절대 잡지 못한다. ErrorBoundary는 JS 스택 안에서만 작동하기 때문이다. 네이티브 레벨(가령 메모리 부족으로 인한 OS 강제 종료나 네이티브 모듈 내부 버그)에서 터진 에러는 자바스크립트 엔진 자체가 멈추기 때문에 JS 코드가 실행될 수 없다. 따라서 Sentry 등을 연동하여 '왜 죽었는지' 로그라도 확실하게 서버로 받아내야 한다.