#Frontend

집 나간 네트워크는 돌아왔는데 React.lazy는 왜 안 돌아올까

집 나간 네트워크는 돌아왔는데 React.lazy는 왜 안 돌아올까
01

Summary

네트워크 복구 후에도 멈춰버린 React.lazy, 모듈 맵 스펙의 비밀과 해결법

HTML 스펙의 모듈 맵 캐시 메커니즘을 파헤치고 새로고침 없는 SPA 환경에서 번들 최적화와 오프라인 안정성을 모두 잡은 노하우

배달의민족 주문접수 웹뷰 팀이 네트워크 단절 후 재연결 시 React.lazy 모듈 로드가 실패하는 복잡한 트러블슈팅 과정을 다룹니다. 브라우저 ES 모듈 맵 표준 명세의 한계로 인해 실패 결과가 캐시되는 현상을 분석하고, fetch 기반의 사전 연결 검사와 부팅 시 사전 로딩 기법을 도입하여 문제 해결과 메인 번들 최적화를 동시에 달성했습니다.

  • 01브라우저 ES 모듈 맵(Module Map)이 실패한 dynamic import 결과를 영구 캐시하는 HTML 표준 스펙 동작 분석
  • 02Vite 공식 문서에서 제안하는 새로고침(reload)을 사용할 수 없는 롱러닝 SPA 웹뷰 환경의 제약사항 극복
  • 03실패 기록이 남지 않는 fetch HEAD 요청으로 연결을 확인한 후 import()를 호출하는 안전 장치 구현
  • 04부팅 직후 주요 라우트 청크를 미리 사전 로딩(Preload)하여 네트워크 단절 시에도 화면 전환 보장
  • 05메인 번들 사이즈 52% 절감(176.9kB -> 84.7kB) 및 ESLint 규칙을 통한 직용 규칙 자화

RECOMMENDATION

네트워크 상태 변화가 잦은 모바일 웹뷰 환경이나 결제·주문 등 새로고침이 불가능한 롱러닝 SPA를 개발하는 프론트엔드 엔지니어에게 강력히 추천합니다. 표준 스펙의 동작원리를 깊이 이해하고 실무적인 방어 로직을 구축하는 좋은 인사이트를 제공합니다.

The Problem

배민주문접수 웹뷰에서 네트워크 OFF 후 ON으로 전환하여 화면을 이동할 때 모듈 로드 에러가 지속적으로 노출되는 현상이 발생했습니다. 원인 조사 결과, 브라우저의 ES 모듈 맵(module map)이 실패한 import 결과를 캐시하고 있었으며, 새로고침이 불가능한 롱러닝 SPA 웹뷰 환경의 특성상 일반적인 복구 방안을 적용할 수 없었습니다.

The Solution

import() 실패가 모듈 맵에 캐시되는 것을 막기 위해, 자유롭게 재요청이 가능한 fetch() HEAD 요청으로 서버 연결 상태를 먼저 확인한 뒤 안전한 시점에만 import()를 호출하도록 통제했습니다. 또한 앱 부팅 직후 주요 화면 모듈을 미리 사전 로드(Preload)하여 모듈 맵을 성공 기록으로 채우는 전략을 도입했습니다.

The Result

네트워크 재연결 후 화면 전환 시 오류 페이지가 발생하는 문제가 해결되었으며, 오프라인 상태에서도 사전 로드된 화면으로 정상 이동이 가능해졌습니다. 또한 모든 모듈을 정적 import할 때와 비교해 메인 번들 크기를 gzip 기준 176.9kB에서 84.7kB로 52% 이상 줄였습니다.

Trade-off

사용하지 않는 화면은 내려받지 않는다는 지연 로딩(Lazy Loading)의 이점을 일부 포기하고 부팅 직후 주요 모듈을 미리 받아오는 오버헤드를 수용하는 대신, 모바일 웹뷰 환경에서의 네트워크 안정성을 확보했습니다.

03

Key Concepts

Concept · 01

Module Map (모듈 맵)

브라우저 환경에서 ES 모듈의 URL을 키로 하여 로드된 모듈과 평가 결과를 캐시하는 document 단위의 내부 캐시 메커니즘입니다. 동일한 모듈이 중복 평가되거나 재요청되는 것을 방지합니다.

  • 과거 HTML 스펙상 fetch 실패(Failed fetch) 상태를 캐시하도록 명시되어 있어 한 번 실패한 URL은 재시도가 불가능했음
  • document가 유지되는 동안 캐시가 지속되므로 새로고침을 하지 않는 한 dynamic import 재시도가 차단되는 원인이 됨
Concept · 02

Code Splitting (코드 스플리팅)

애플리케이션의 코드를 단일 번들로 묶는 대신 필요에 따라 동적으로 로드할 수 있는 여러 청크(Chunk) 파일로 분할하는 빌드 최적화 기법입니다.

  • React.lazy와 dynamic import()를 사용해 라우트 단위로 모듈 분할을 적용함
  • 정적 import 대비 초기 로딩 시 다운로드할 메인 번들 파일의 크기를 176.9kB에서 84.7kB로 절반 이상 감축함
Concept · 03

Dynamic Import (동적 임포트)

런타임 환경에서 필요한 시점에 텍스트 모듈이나 청크 파일을 비동기적으로 불러오는 JavaScript 구문(import())으로, Promise 객체를 반환합니다.

  • 모듈 맵에 실패 상태가 등록된 경우 네트워크 요청을 아예 발생시키지 않고 즉시 에러를 반환함
  • fetch() 요청을 선행하여 네트워크 성공을 확인한 뒤 안전하게 dynamic import를 실행하도록 흐름을 통제함