오늘은 클라이언트에서 에러가 발생하면 에러 로그와 사용자가
에러 화면에서 추가 동작이 가능하도록 할 수 있는 ErrorBoundary.tsx를 소개 드리겠습니다.
참고자료 : https://nextjs.org/docs/advanced-features/error-handling
저희 사이트의 경우 웹뷰 기반의 앱도 함께 사용 중 입니다.
그렇기 때문에 사용자가 클라이언트 에러가 발생하면 해당 화면에서 빠져나올 방법이 없었습니다.
실행 핸드폰 어플에서 클라이언트 에러가 발생하면 보이는 화면.
사실 이미 구현이 되었어야 했다고 생각했지만 NextJS로 전환한지 얼마 지나지 않았고
아직 PHP->React전환 작업이 한창 남았기 때문에 인지하지 못하고 있었다.
클라이언트 화면에서 에러 발생후 고객이 해당 화면에서 갇혔다는 문의가 들어와 개발팀에게 수정 요청을 하였다.
요청을 받고 제일 먼저 든 생각이 당연히 NextJS에서 에러 미들웨어 기능이 있지 않을까? 라고 생각을 하고 NextJs 공식 문서를 찾았고
역시나 없을 수가 없었다.
NextJS 공식문서대로 ErrorBoundary.tsx 생성후
_app.tsx에 적용해주었다.
간단한 설명으로는 ErrorBoundary.tsx 코드내부에서 오류가 발생한다면
에러에 따른 jsx를 리턴하고 오류가 발생하지 않는다면 children 자식 컴포넌트들을 정상적으로 실행시킨다.
interface State {
hasError: boolean;
}
const skippableErrors = /Googlebot|AdsBot/;
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
handleBack = async () => {
const isLogin = haveAccessToken();
const url = 'js_data/main_user_info';
const { data } = isLogin && (await fetchFromIBrand<{ data: UserInfoItf }>(url));
await sendErrorToSynology({
message: `url: ${location.href} 에러 페이지에서 뒤로가기를 눌렀습니다.
\nuser: ${data ? JSON.stringify(data) : '비로그인'}
\ndevice: ${loggerDeviceCategory[getLoggerDeviceType()]}`,
});
window.history.back();
setTimeout(() => window.location.reload(), 300);
};
handleGoMain = async () => {
const isLogin = haveAccessToken();
const url = 'js_data/main_user_info';
const { data } = isLogin && (await fetchFromIBrand<{ data: UserInfoItf }>(url));
await sendErrorToSynology({
message: `url: ${location.href} 에러 페이지에서 메인사이트로 가기 눌렀습니다.
\nuser: ${data ? JSON.stringify(data) : '비로그인'}
\ndevice: ${loggerDeviceCategory[getLoggerDeviceType()]}`,
});
location.href = '/main/index';
};
async componentDidCatch(error, errorInfo) {
const isLogin = haveAccessToken();
const url = 'js_data/main_user_info';
const { data } = isLogin && (await fetchFromIBrand<{ data: UserInfoItf }>(url));
const beforeUrl = global.window && getStore('beforeUrl');
const userAgent = JSON.stringify(global.window && navigator.userAgent);
if (!skippableErrors.test(userAgent)) {
await sendErrorToSynology({
message: `url: ${location.href}\nerror: ${JSON.stringify(error?.message)}\nuser: ${
data ? JSON.stringify(data) : '비로그인'
}\nbeforeUrl: ${beforeUrl}
\ndevice: ${loggerDeviceCategory[getLoggerDeviceType()]}
\nuserAgent: ${userAgent}`,
});
}
}
render(): ReactNode {
const { hasError } = this.state;
const { children } = this.props;
if (!hasError) {
return children;
}
return (
<ErrorContainer>
<ImgBox>
<Image layout='intrinsic' width={400} height={200} src={logo.src} alt='logo' />
</ImgBox>
<div className='title'>
<h3>서비스 에러가 발생 하였습니다.</h3>
<p>
<br />
죄송합니다.
<br />
기술적인 문제로
<br />
일시적으로 서비스를 사용할 수 없습니다.
<br />
관리자에게 요청하여
<br />
빠른 시간 안에 해결 하겠습니다.
</p>
</div>
<BtnBox>
<ReloadButton onClick={this.handleBack}>
<span>뒤로가기</span>
</ReloadButton>
<ReloadButton onClick={this.handleGoMain}>
<span>메인 사이트로 돌아가기</span>
</ReloadButton>
</BtnBox>
</ErrorContainer>
);
}
}
export { ErrorBoundary };
간단한 에러화면 UI와 에러 발생 시 회사 내부 오류쳇(시놀로지)로그, 사용자가 추가 행동(뒤로가기,메인으로가기)로그등 알 수 있는 코드이다.
최근 리액트를 개발하시는 분들은 해당 코드를 보고 의문이 있을 것이다.
왜냐하면 hooks 컴포넌트가 아닌 class 컴포넌트를 사용했기 때문이다.
해당 기능에 대해서는 React17까지도 Hook에 대한 기능을 지원하지 않았고 React18에서는 잘 모르겠지만
현재 NextJS공식문서에도 class컴포넌트로 설명한걸로 보아 아직까지도 지원을 하지 않는 모양이다.