@react-google-maps/api의 마커 깜빡임과 삽질

Overview

React 18이상의 환경에서 @react-google-maps/api를 사용할 때 마커가 깜빡이는 현상의 원인과 해결 방법을 정리했다. 리렌더링 최적화부터 React 18의 동시성 렌더링 이슈까지, 실제 문제 해결 과정을 통해 OverlayViewF 컴포넌트 사용법을 알아본다.

프로젝트에서 @react-google-maps/api 라이브러리를 사용해 커스텀 마커 컴포넌트를 지도 위에 올리는 작업을 하고 있었다. 기능 자체는 복잡할 게 없었다. 그런데 지도를 드래그하거나 줌을 조절할 때마다, 마커들이 미세하게 깜빡이는 현상과 마주했다. 개발자라면 한 번쯤 겪어봤을 법한 깜빡임. 당연히 리렌더링이 원인이라고 생각했다.


첫 번째 삽질: 범인은 리렌더링이다

가장 먼저 한 일은 커스텀 마커 컴포넌트에서 로그를 확인해보는 것이었다. 예상대로 지도를 움직일 때마다 로그가 계속해서 찍혔다. 리렌더링의 원인은 명확했다. Map 컴포넌트가 다시 렌더링되면서 locations 배열을 기반으로 마커를 새로 만들어내고 있었고, 이 과정에서 onClick 핸들러 같은 함수들도 재생성되고 있었다.

전형적인 최적화 문제라고 판단하고, 교과서적인 해결책을 적용했다.

// Before: 인라인 함수로 인한 불필요한 리렌더링
<GoogleMap>
  {locations.map(loc => (
    <CustomMarker
      key={loc.id}
      location={loc}
      onClick={() => router.push(`/path/${loc.id}`)} // 매번 새 함수 생성
    />
  ))}
</GoogleMap>

useCallback으로 이벤트 핸들러를 메모이제이션하고, React.memoCustomMarker를 감쌌다. 마커 컴포넌트 리스트 자체도 useMemo로 묶어 불필요한 연산을 막았다.

// After: useCallback + React.memo로 최적화
const handleMarkerClick = useCallback((locId) => {
  router.push(`/path/${locId}`);
}, [router]);
 
const markers = useMemo(() =>
  locations.map(loc => (
    <CustomMarker key={loc.id} location={loc} onClick={handleMarkerClick} />
  )), [locations, handleMarkerClick]
);
 
export default React.memo(function CustomMarker({ location, onClick }) {
  return (
    <OverlayView position={location.position}>
      <div onClick={() => onClick(location.id)}>{location.name}</div>
    </OverlayView>
  );
});

이 코드가 바로 처음 문제를 해결했다고 착각했던, 그리고 최종적으로도 유지하게 된 최적화 코드다. 이제 로그는 더 이상 찍히지 않았다. 리렌더링은 분명히 막았지만, 결과는 달라지지 않았다. 마커는 여전히 깜빡였다. 최적화는 성공했지만 문제의 근본 원인은 다른 곳에 있었다.


진짜 원인: React 18 동시성 렌더링

메모이제이션으로도 해결되지 않는 깜빡임. 문제의 진짜 원인은 React 18의 동시성 렌더링과 구형 OverlayView 컴포넌트의 호환성 이슈였다.

React 18부터 도입된 동시성 렌더링은 렌더링 효율을 극대화하기 위해 렌더링 중간에 작업을 중단하거나 여러 상태 업데이트를 한 번에 묶어서 처리한다. 그런데 OverlayView처럼 React 외부에서 직접 DOM을 제어하는 컴포넌트는 이 새로운 렌더링 흐름과 타이밍이 어긋나기 쉽다.

이 타이밍 충돌이 바로 마커가 순간적으로 DOM에서 사라졌다가 다시 나타나는 '깜빡임' 현상의 진짜 원인이었던 것이다.


해결책: 어이없을 만큼 간단한

원인을 알고 나니 해결책은 놀랍도록 간단했다. @react-google-maps/api 라이브러리는 이미 React 18에 대응하기 위해 OverlayView의 'F'(Functional) 버전인 OverlayViewF 컴포넌트를 제공하고 있었다.

기존 CustomMarker 내부의 코드를 아래와 같이 단 한 글자만 수정했다.

// 단 한 글자만 바꾸면 끝!
 
// Before
import { OverlayView } from '@react-google-maps/api';
 
// After  
import { OverlayViewF } from '@react-google-maps/api';

그 결과, 깜빡임 현상은 완전히 사라졌다.

OverlayViewF는 이름에서 알 수 있듯 함수형 컴포넌트(Functional Component) 기반으로, React 18의 새로운 렌더링 방식과 Hook 기반의 라이프사이클에 맞춰 새롭게 구현된 컴포넌트다.

덕분에 React 18의 렌더링 흐름과 Google Maps API의 DOM 업데이트 타이밍을 훨씬 더 잘 동기화하여 깜빡임 없이 안정적으로 동작할 수 있었다.


결론

TL;DR: React 18에서 Google Maps 마커가 깜빡인다면 OverlayViewOverlayViewF로 바꿔보자.

익숙한 현상(깜빡임)이 항상 익숙한 원인(리렌더링) 때문은 아니다. React 18의 동시성 렌더링과 구형 컴포넌트의 호환성 문제일 수 있으니, 라이브러리가 제공하는 최신 컴포넌트를 확인해보자.