Next.js
React 모달이 터치 환경에서 닫혀버릴 때
모달이 로컬에서는 멀쩡한데 프로덕션에서 이상해진다면, 포커스와 이벤트 흐름을 먼저 확인하자.
로컬에서 모달을 열었을 때 순조롭게 동작하는데, 배포된 환경에서 갑자기 다르게 보이는 경험이 있다. 특히 터치 기기에서는 배경을 누르면 바로 닫혀 버리거나, 키보드 포커스 트래핑이 제대로 작동하지 않는다. 이런 상황은 보통 프론트엔드 UX 흐름 어딘가에 작은 불일치가 있을 때 생긴다.
먼저 보는 것: 렌더링 환경 차이
로컬 개발과 프로덕션의 차이를 가장 빠르게 좁혀야 한다. 로컬에서는 HMR 때문에 여러 번 리렌더링되지만, 프로덕션에서는 한 번만 마운트된다.
npm run build
npm run start # 프로덕션 빌드로 로컬에서 직접 확인
모달이 닫히는 원인 좁히기
세 가지 가능성을 순서대로 확인한다.
1. 배경 클릭 핸들러의 스코프
배경을 감싼 div에 onClick 핸들러가 있으면, 이벤트 버블링 때문에 의도치 않게 모달이 닫힐 수 있다. 모달 내용을 클릭해도 버블링되면서 배경 클릭으로 해석된다.
// 잘못된 예
<div onClick={() => setOpen(false)}>
<div className="modal-content">...</div>
</div>
// 올바른 예
<div onClick={(e) => e.currentTarget === e.target && setOpen(false)}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
...
</div>
</div>
2. 포커스 트래핑 누락 Tab 키를 눌렀을 때 포커스가 모달 밖으로 나가면, 사용자가 실수로 배경을 조작할 가능성이 높다.
const handleKeyDown = (e) => {
if (e.key === 'Escape') setOpen(false);
if (e.key === 'Tab') {
const focusables = modalRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]'
);
const first = focusables[0];
const last = focusables[focusables.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
};
3. 모바일에서의 터치 이벤트
Lorem 기기에서 터치하면 click 이벤트 전에 touchmove가 발생할 수 있다. 이 이벤트를 방지하지 않으면 모달이 흔들린다.
const modalRef = useRef(null);
useEffect(() => {
const prevent = (e) => e.preventDefault();
const element = modalRef.current;
if (element) {
element.addEventListener('touchmove', prevent, { passive: false });
return () => element.removeEventListener('touchmove', prevent);
}
}, []);
실제 동작 확인 순서
- 프로덕션 빌드로 로컬에서 실행해 문제를 재현한다.
- 개발자 도구에서 렌더링 프로파일링으로 모달이 닫히는 시점에 어떤 컴포넌트가 리렌더링되는지 확인한다.
- 네트워크 탭에서 모달 열고 닫을 때 불필요한 요청이 발생하는지 본다.
배포 전 체크리스트
- 모달 배경과 내용 div가 명확히 분리되었는가
stopPropagation과currentTarget === target비교가 모두 있는가- 모바일에서 직접 테스트했는가 (에뮬레이터가 아닌 실제 기기)
- Escape 키로도 닫히는가
간단해 보이지만 프로덕션 환경에서는 캐싱, 번들 최적화, CSR vs SSR 등 여러 요소가 겹쳐서 복잡해진다. 로컬과 운영의 작은 차이를 하나씩 확인하면 대부분의 모달 문제는 해결된다.