onClick 이벤트 핸들러로 발생시키는 에러는 에러로 못 잡는다고?
Next.js에서는 컴포넌트 내부에서 발생한 오류를 감지하고 에러 페이지(error.tsx)를 렌더링하는 기능이 있습니다.
에러 페이지가 렌더링 되는 지 확인하기 위해 onClick 이벤트를 통해 이벤트를 발생시켜봤습니다.
🎯 문제 상황: onClick을 통해 throw Error를 했는데 에러 페이지가 렌더링되지 않음
클라이언트 컴포넌트 내에서
<button onClick={()=>{throw new Error('클릭이벤트에서 에러 발생')}}>에러 발생 버튼</button>
이렇게 에러를 던져주는 코드를 작성한 후 브라우저에서 시도해봤습니다.

에러를 발생시키기만 할 뿐 error.tsx는 렌더링 되지 않았습니다.
클릭 이벤트를 통해 발생한 에러는 에러 바운더리가 감지하지 않는다는 사실을 알 수 있습니다.
그렇다면 왜 에러 바운더리가 감지하지 못할까요?
에러 경계(Error Boundaries) – React
A JavaScript library for building user interfaces
ko.legacy.reactjs.org
에러 경계는 다음과 같은 에러는 포착하지 않습니다.
- 이벤트 핸들러
- 비동기적 코드 (예: setTimeout 혹은 requestAnimationFrame 콜백)
- 서버 사이드 렌더링
- 자식에서가 아닌 에러 경계 자체에서 발생하는 에러
리액트 공식문서에서 이벤트 핸들러는 에러 바운더리에 포함되지 않는다고 되어 있습니다.
그럼 이벤트 핸들러는 도대체 어디서 처리되길래 에러 바운더리를 벗어나 있는 것일까요?
리액트의 이벤트 핸들러는 이벤트 위임을 통해 document 레벨에서 모두 처리하고 있습니다.

리액트의 에러 바운더리는 컴포넌트 트리에서 발생하는 렌더링 및 라이프 사이클만을 감지합니다.
그러므로 그보다 상위에 있는 이벤트 핸들러를 통한 에러는 감지하지 못하는 것입니다.
그렇다면 상태 변화를 통해 리렌더링을 발생시켜 주고 그 상황에서 에러를 발생시켜 봅시다.
const RotationChampionList = () => {
const [flag, setFlag] = useState(false);
if (flag) {
throw new Error("error 발생");
}
return (
<div className="flex flex-wrap gap-4 justify-between">
<button onClick={() => {setFlag(true);}}>
에러 발생 버튼
</button>
</div>
);
};

- 여기서는 상태 변화(setFlag(true))로 인해 리렌더링이 발생하고,
- flag 값이 true가 되면서 <div> 내부에서 예외가 발생합니다.
- React의 렌더링 과정 중 예외가 발생하면, Next.js의 에러 바운더리가 이를 감지하고 error.tsx를 렌더링합니다.
라고 마무리를 하려고 했으나 그것이 문제가 아니었습니다...
일단 위처럼 렌더링 과정 중 발생하는 예외는 에러 바운더리가 감지하는 것이 맞습니다.
React의 이벤트 핸들러는 document 레벨에서 위임되며, 이로 인해 이벤트에서 발생한 에러는 React의 렌더링 흐름과 분리됩니다. 그래서 에러 바운더리가 이벤트 핸들러에서 발생한 에러를 감지하지 못합니다. 이벤트 핸들러가 비동기적으로 처리되어 React의 상태 업데이트와는 독립적인 흐름을 가지기 때문입니다.
여기서 나오는 TTV...TTI...
클라이언트 사이드 렌더링(CSR)에서는 React가 처음 렌더링할 때 버튼이 이벤트 핸들러 없이 표시되고, 이후 hydrate 과정을 통해 비동기적으로 이벤트 핸들러가 적용되어 버튼이 활성화됩니다.
파면 팔수록 심오한 세계로....