들어가며 파트너(업주)님이 영업 내내 주문을 받는 배민주문접수 웹뷰를 개발하던 중 QA 과정에서 네트워크 OFF -> ON으로 변경 후 다른 화면으로 이동 시 오류 화면이 노출된다는 티켓을 전달받았습니다. 원인을 파악해 보니 브라우저가 모듈 로드 실패를 기억하고 있었고, 이는 버그가 아니라 HTML 스펙에 명시된 동작이었습니다. 더 곤란한 것은 프런트엔드 빌드 도구인 Vite 공식 문서가 안내하는 해결책 중 […]
The post 집 나간 네트워크는 돌아왔는데 React.lazy는 왜 안 돌아올까 first appeared on 우아한형제들 기술블로그. || 들어가며
파트너(업주)님이 영업 내내 주문을 받는 배민주문접수 웹뷰를 개발하던 중 QA 과정에서 네트워크 OFF -> ON으로 변경 후 다른 화면으로 이동 시 오류 화면이 노출된다는 티켓을 전달받았습니다.
원인을 파악해 보니 브라우저가 모듈 로드 실패를 기억하고 있었고, 이는 버그가 아니라 HTML 스펙에 명시된 동작이었습니다. 더 곤란한 것은 프런트엔드 빌드 도구인 Vite 공식 문서가 안내하는 해결책 중 하나가 새로고침이었다는 점입니다. 배민주문접수 웹뷰는 새로고침을 하면 앱 상태가 초기화되는 SPA(single page application)라 그 방법을 사용할 수 없었습니다. React.lazy 사용을 통째로 포기했다가 되살린 과정을 공유합니다.
실패를 기억하는 곳을 찾아가기
네트워크는 복구됐는데 화면은 그대로였다
배포일이 이틀 앞으로 다가온 시점이었습니다. 지체할 시간 없이 곧바로 티켓의 증상 재현에 나섰습니다.
앱을 켠 상태에서 네트워크 연결을 해제합니다.
다른 화면으로 이동합니다.
네트워크를 다시 연결합니다.
"오류가 발생했습니다." 페이지가 보입니다. 어느 화면으로 이동해도, 몇 번을 다시 시도해도 같습니다. 네트워크는 이미 정상인데 왜 계속 실패하는 것인지 궁금했습니다.
Sentry를 확인해 보니 모듈을 받아오지 못했다는 에러가 여러 화면에 걸쳐 쌓여 있었습니다.
당시 배민주문접수 웹뷰는 화면(라우트) 단위로 React.lazy와 dynamic import를 사용하여 코드 스플리팅을 하고 있었습니다.
// pages/Foo/index.ts
export default lazy(() => import('./ui/Foo'));
네트워크 연결이 끊어진 동안 아직 로드되지 않은 화면으로 이동하면 dynamic import가 실패했습니다. 여기까지는 예측할 수 있는 실패였습니다. 문제는 네트워크가 돌아온 뒤에도 계속 실패한다는 점이었습니다.
React.lazy 재시도가 통하지 않았다
처음 의심한 것은 React.lazy였습니다. React 공식 문서에는 Promise와 그 결과가 캐시되어 load 함수가 중복 호출되지 않으며, 실패 시 가장 가까운 ErrorBoundary로 에러를 전달한다고 안내합니다. 한 번 실패한 뒤에 다시 시도할 방법이 있는지 문서만으로 알 수 없어서 React 리포지토리의 ReactLazy.js를 열어 봤습니다.
React.lazy 컴포넌트는 내부에 상태 하나를 들고 있습니다. Uninitialized에서 시작해 Pending을 거쳐 Resolved나 Rejected로 가는데, 한 번 Rejected 상태가 되면 다른 상태로 전환될 수 없습니다. 그 뒤로는 렌더링될 때마다 캐시된 에러를 던질 뿐이고, React.lazy에 넘긴 함수는 다시 호출되지 않습니다.
이 증상을 검색했을 때 가장 먼저 접하게 되는 해법은 React.lazy에 전달한 함수가 실패하면 import()를 다시 호출하는 재시도 패턴입니다. AI가 가장 먼저 제안해 준 해결책 역시 이 재시도 패턴이었습니다. 배민주문접수 웹뷰에도 그대로 적용해 봤습니다.
그러나 동작하지 않았습니다. 더 이상했던 점은 실패하는 양상이었습니다. 재시도가 분명히 새 import()를 호출하는데도 네트워크 탭에 요청이 기록되지 않았습니다. 브라우저가 요청을 시도하지도 않고 즉시 같은 에러를 돌려주고 있었던 것입니다. 네트워


