React Native + 카카오맵 웹뷰 삽질

Eugenius1st·2026년 6월 30일

React Native

목록 보기
23/23

HealingTrip 카카오맵 WebView 스터디

React Native WebView + Kakao Maps JS SDK 로컬 개발 중 겪은 문제와 해결 과정 정리

대상: HealingTrip FE (Expo + react-native-webview + Vite 지도 페이지)


1. 배경 / 목표

아키텍처 원칙

WebView = 멍청한 지도 렌더러, 데이터·상태·UI = 전부 RN

영역담당
장소 fetch, 필터, 바텀시트, 검색React Native
지도 렌더링, 마커, 카메라WebView (Kakao JS SDK)
RN ↔ WebView 통신{ type, payload } 메시지 프로토콜

구성

web/map/ ← Vite로 빌드하는 지도 전용 페이지 (main.js)

components/map/ ← MapWebView.tsx

hooks/ ← useMapBridge.ts (브릿지 + MAP_READY 큐)

app/(tabs)/map/ ← 지도 탭 화면

지도 페이지는 EXPO_PUBLIC_MAP_URL (로컬: http://localhost:5174)로 WebView에 로드.


2. 겪었던 증상 (시간순)

증상 A: 빨간/빈 화면만 보임

  • Mac 브라우저 http://localhost:5174 → 지도 OK
  • RN WebView → HTML만 보이거나 지도 없음

초기 가설 (틀린 것들)

  • ❌ HTTPS가 아니라서? → 같은 HTTP인데 다른 Vite 앱(5174)은 WebView에서 됨
  • ❌ localhost 접속 불가? → load start/end 로그 나옴, HTML 렌더됨
  • ❌ 시뮬레이터 문제? → iOS 시뮬레이터 Safari에서는 동일 URL OK

증상 B: HTML/텍스트는 보이는데 지도만 안 보임

  • WebView에서 <p> 디버그 텍스트는 보임
  • 시뮬레이터 Safari에서는 텍스트 + 지도 둘 다 OK
  • → WebView 자체 문제가 아니라, 카카오 SDK 초기화/렌더링 문제

증상 C: Metro 로그로 단계 추적

로그의미
PAGE_BOOTmain.js 실행됨
SDK_LOADINGSDK script 요청 시작
SDK_SCRIPT_LOADEDsdk.js 로드 완료
MAP_INIT_STARTinitMap 진입 시도
MAP_INIT_POLL + hasMapCtor: falsekakao.maps는 있는데 Map 생성자가 영원히 안 생김
MAP_INIT_ATTEMPT → MAP_READY드디어 성공

3. 근본 원인

한 줄 요약

카카오 SDK 1차 스크립트(dapi.kakao.com)는 로드됐지만, Map 생성자를 등록하는 2차 모듈(daumcdn.net 등) 요청이 iOS WebView ATS에 막혔다.

SDK 로딩 2단계

1단계: sdk.js (dapi.kakao.com) → kakao.maps + load() 함수만 생김 ✅

2단계: 내부 모듈 (daumcdn.net) → kakao.maps.Map 생성자 등록 ❌ (ATS 차단)

그래서 디버그 로그가 이렇게 나왔다:

{

"hasKakao": true,

"hasMaps": true,

"hasMapCtor": false,

"hasLoad": true,

"mapsKeys": ["onloadcallbacks", "readyState", "load", "apikey", ...]

}

껍데기만 있고 Map이 없는 상태. kakao.maps.load() 콜백도 영원히 안 불림.

Safari vs RN WebView

SafariRN WebView (WKWebView)
sdk.js
daumcdn.net 모듈❌ (ATS)
kakao.maps.Map

같은 URL, 같은 HTML인데 WKWebView만 실패 → 네트워크 정책(ATS) 차이.


4. 해결 방법

핵심 fix: app.json ATS 설정

Expo app.json → prebuild → ios/Info.plist에 반영.

"NSAppTransportSecurity": {

"NSAllowsLocalNetworking": true,

"NSExceptionDomains": {

"kakao.com": {

"NSIncludesSubdomains": true,

"NSExceptionAllowsInsecureHTTPLoads": true,

"NSExceptionRequiresForwardSecrecy": false

},

"daum.net": { ... 동일 ... },

"daumcdn.net": { ... 동일 ... }

}

}

도메인용도
kakao.comSDK API (dapi.kakao.com)
daum.net / daumcdn.net지도 타일·내부 모듈 CDN

Android 로컬 dev: "usesCleartextTraffic": true

네이티브 재빌드 필수

app.json 변경은 Metro reload로 반영 안 됨.

npx expo prebuild --clean --platform ios # Info.plist 재생성

npx expo run:ios --no-build-cache # 재빌드

--clear는 expo start 전용. run:ios는 --no-build-cache.

재빌드 후:

MAP_INIT_ATTEMPT → MAP_READY {"width":402,"height":690}


5. 디버깅 과정에서 시도했던 것들

효과 있었던 것

조치효과
app.json ATS (daumcdn.net)근본 해결
단계별 postMessage 로그원인 좁히기 (hasMapCtor)
main.js에서 dynamic script loadHTML onload 속성보다 WebView에서 안정적
#map flex 100% + map.relayout()레이아웃 이슈 예방
domStorageEnabled 등 WebView props카카오맵 일반 권장 설정

효과 없었거나 2차적인 것

조치판단
HTTP vs HTTPS 가설로컬 HTTP 다른 앱은 WebView OK → 원인 아님
messageQueue / 폴링 / maps.load 재시도ATS 해결 전엔 Map 자체가 안 생김
autoload=true/false 전환ATS 없으면 둘 다 Map 안 생김
HTML <script>WKWebView에서 콜백 미호출 cases

6. 브릿지 동작 원리

RN과 WebView는 서로 다른 JS 런타임 → 함수 직접 호출 불가, 메시지 프로토콜 사용.

RN → WebView

mapRef.current?.post({ type: 'SET_MARKERS', payload: markers });

↓ injectJavaScript로 WebView 안에서 실행:

window.__handleNative({"type":"SET_MARKERS","payload":[...]}); true;

↓ main.js:

window.__handleNative = (msg) => {

switch (msg.type) {

case 'SET_MARKERS': renderMarkers(msg.payload); break;

// ...

}

};

HTTP 요청이 아니라, RN이 WebView JS 컨텍스트에 코드를 주입(inject)하는 것.

WebView → RN

window.ReactNativeWebView.postMessage(JSON.stringify(msg));

↓ RN onMessage → useMapBridge switch

MAP_READY 큐

MAP_READY 전에 보낸 post()는 큐에 쌓았다가 ready 후 flush. RN은 타이밍 신경 안 써도 됨.

WebView 안에 React?

안 씀. WebView = 마커 + 카메라만. 검색/바텀시트/필터는 RN. vanilla JS + types/map.ts 프로토콜이 적합.


7. 최종 파일별 역할

파일역할되돌리면 안 되는 것
app.jsoniOS ATS, Android cleartextdaum.net / daumcdn.net 예외
web/map/index.html#map 레이아웃, __KAKAO_JS_KEYflex 100%, key 주입
web/map/main.js브릿지 + 카카오 init__handleNativebootKakaosend
web/map/vite.config.js키 치환, port 5174inject-kakao-key 플러그인
hooks/useMapBridge.tspost/onMessage, MAP_READY 큐
components/map/MapWebView.tsxWebView 래퍼domStorageEnabled, EXPO_PUBLIC_MAP_URL

8. 팀 온보딩 체크리스트

로컬 dev 시작

  1. web/map/.env → VITE_KAKAO_JS_KEY
  2. .env → EXPO_PUBLIC_MAP_URL=http://localhost:5174
  3. 카카오 콘솔 → http://localhost:5174 도메인 등록
  4. cd web/map && npm run dev
  5. npx expo run:ios (최초 또는 app.json 변경 후 prebuild)

지도 안 뜰 때

  • Vite 5174 실행 중?
  • Metro에서 MAP_READY / ERROR 확인?
  • app.json ATS 커밋돼 있고 네이티브 재빌드 했?
  • Safari(시뮬레이터)에서 localhost:5174 OK? → WebView만 fail이면 ATS/rebuild 의심

prebuild --clean 하면 ATS 날아가?

아님. app.json이 git에 있으면 clean 후에도 Info.plist에 다시 생성됨.

날아가는 건 Xcode에서 Info.plist만 수기 수정한 경우.


9. 배운 것 (Takeaways)

  1. WebView 문제 vs 지도 문제 구분

    HTML 렌더 + PAGE_BOOT OK → WebView 연결 OK. 지도만 fail → SDK/ATS/레이아웃.

  2. Safari OK + RN WebView fail

    같은 URL이면 SDK 도메인보다 iOS ATS / WKWebView 네트워크 정책 먼저 의심.

  3. 카카오 SDK는 2단계 로드

    sdk.js만 OK ≠ 지도 OK. hasMapCtor: false = 2차 모듈 실패.

  4. Expo에서 네이티브 설정 = app.json

    Info.plist 수기 수정 < app.json 커밋 + prebuild. 실무에서도 흔한 패턴.

  5. 디버그는 postMessage 단계 로그가 최고

    injectJavaScript / postMessage 경계에서 { type } 찍으면 원인 빠르게 좁혀짐.

  6. HTTPS 배포 URL은 로컬 WebView 디버깅 우회책

    아키텍처 문서 권장대로 배포 URL 쓰면 ATS 이슈 적음. 로컬 dev는 ATS 설정 필요.


10. 참고 링크

  • react-native-webview — Communicating with JS
  • Expo — App Transport Security
  • 카카오맵 JS SDK — 도메인 등록
  • 프로젝트 내부: docs/specs/map-webview-architecture.md

11. 한 줄 회고

WebView + 카카오맵에서 "지도만 안 보인다"는 증상의 80%는 bridge 타이밍이 아니라, SDK 2차 모듈 CDN이 iOS ATS에 막힌 것이었다.

브라우저/Safari와 WebView의 차이를 먼저 의심하고, postMessage로 SDK 로딩 단계를 계측하면 빠르게 찾을 수 있다.

profile
자신만의 속도로

0개의 댓글