Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
Tags
- 지도성능최적화
- Slack 알림
- processterminated
- Android SDK
- 보안규칙
- firebaseauthentication
- 익명로그인
- ReactNative
- markerclusterer
- React Native
- oncontentprocessdidterminate
- 난독화
- firebase
- slack-webhook
- Firestore
- webview
- React
- react-app-rewired
- KaKaoMap
- 보안
- slack 배포 알림
- Github-Actions
- react native upgrade helper
- react native 업그레이드
Archives
- Today
- Total
작은프론트이야기
React Kakao Map에서 수천 개의 마커를 렌더링할 때 성능 최적화 본문
프론트엔드 프로젝트에서 차량 위치를 표시하는 기능을 구현하던 중, 사업장마다 수천 대의 차량 정보를 Kakao Map에 마커로 표시해야 했다. 초반에는 react-kakao-maps-sdk의 <MapMarker>를 그대로 사용했으나, 마커가 많아질수록 다음과 같은 문제가 발생했다.
- 2천 개 이상의 마커를 렌더링할 경우 페이지 전체가 멈추거나 느려지는 현상
- 지도 줌 조작 후 사업장 변경 시 무한 로딩 현상 또는 이미지 요청 지연
- 다른 페이지로 이동 시에도 대기 중이던 마커 이미지 요청이 남아있어 성능 저하
원인 분석
기존 구현은 다음과 같았다.
<MarkerClusterer>
{carLocationList.map((item) => (
<MapMarker position={item.position} />
))}
</MarkerClusterer>
즉, 마커 하나당 <MapMarker> 컴포넌트가 생성되며 React가 이를 모두 렌더링하게 했다. 이는 렌더링 성능과 가비지 컬렉션 처리에 심각한 병목을 일으켰다.
개선 목표
- 수천 개의 마커를 효율적으로 렌더링할 것
- React 상태 관리 최소화 (마커 직접 렌더링 제거)
- 기존처럼 마커 클릭 시 상세 팝업 기능 유지
해결 과정
1. <MapMarker> 제거 → Kakao API 직접 사용
React가 마커를 렌더링하지 않도록 하고, useEffect에서 직접 kakao.maps.Marker 객체를 만들어 MarkerClusterer에 추가하도록 변경했다.
const newMarkers = carLocationList.map(item => {
const marker = new kakao.maps.Marker({
position: new kakao.maps.LatLng(item.location.la, item.location.lo),
image: markerImage,
});
marker.myData = item; // 팝업 데이터 저장
kakao.maps.event.addListener(marker, 'click', () => {
setOverlayPosition({ lat: item.location.la, lng: item.location.lo });
setOverlayItem(item);
setIsOverlayVisible(true);
});
return marker;
});
cluster.addMarkers(newMarkers);
setMarkerInstances(newMarkers);
2. 마커 이미지 useMemo로 캐싱
const markerImage = useMemo(() => {
return new kakao.maps.MarkerImage(RedDotIcon, new kakao.maps.Size(16, 16), {
offset: new kakao.maps.Point(8, 16),
});
}, []);
이렇게 처리해 마커를 여러 번 렌더링하더라도 동일한 이미지 인스턴스를 재사용하여 이미지 중복 요청을 방지했다.
3. 팝업(CustomOverlayMap) 기능 유지
기존 <MapMarker> 내부의 children으로 처리하던 커스텀 팝업도 CustomOverlayMap을 활용해 다음과 같이 표시했다:
{isOverlayVisible && overlayPosition && (
<CustomOverlayMap position={overlayPosition} yAnchor={1}>
<div className="custom-marker">
<div className="mb-2">
<img
src={CloseIcon}
alt="닫기"
className="close cursor-pointer"
onClick={() => {
setIsOverlayVisible(false);
setOverlayItem(null);
}}
/>
</div>
<div className="marker-data">
{overlayItem.vehicleNumber} | {overlayItem.vehicleType}<br />
{overlayItem.vehicleModel}
</div>
<img className="polygon" src={PolygonIcon} alt="마커-polygon" />
</div>
</CustomOverlayMap>
)}
마커를 클릭하면 팝업이 해당 위치에 나타나도록 했고, 클러스터를 클릭했을 때는 줌 확대만 되도록 유지했다.
4. 정리 및 메모리 누수 방지
페이지 이동 시 마커와 클러스터 모두 정리해 메모리 누수를 방지했다:
useEffect(() => {
return () => {
markerInstances.forEach(marker => marker.setMap(null));
marketClusterRef.current?.clear();
};
}, []);
결과
- 수천 개의 마커를 표시해도 부드럽게 작동했다.
- 사업장 변경 시 이미지 로딩 문제를 해소했다.
- 클러스터 클릭 시 기본 줌 동작은 유지하고, 실제 마커를 클릭했을 때만 팝업이 노출되도록 처리했다.
마무리
이번 작업은 퍼포먼스가 민감한 맵 UI에서 "React와 DOM 조작의 적절한 분리"가 얼마나 중요한지를 보여준 예시였다.
특히 지도와 같이 외부 SDK에서 상태를 관리하는 컴포넌트에서는 React의 선언적 방식보다 Imperative 방식이 유리한 경우가 많다.
참고자료
'문제 해결' 카테고리의 다른 글
React 프로젝트 보안 강화 난독화 및 소스코드 비노출 처리 (1) | 2025.05.21 |
---|---|
React Native WebView Process Terminated 대응기 (1) | 2025.04.24 |
Gradle 8, SDK 34 대응을 위한 React Native 버전 업그레이드 (1) | 2025.04.21 |
FirebaseError: Missing or insufficient permissions 에러 (feat. 익명 로그인 & 보안 규칙 설정) (0) | 2025.04.15 |