Next.js, Typescript 환경에서 Kakao Maps API 사용하기

카카오 MAP API를 사용하기 위해선 다음과 같이 코드를 작성해야 합니다.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Kakao 지도 시작하기</title>
  </head>
  <body>
    <div id="map" style="width:500px;height:400px;"></div>
    <script
      type="text/javascript"
      src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다."
    ></script>
    <script>
      var container = document.getElementById('map');
      var options = {
        center: new kakao.maps.LatLng(33.450701, 126.570667),
        level: 3,
      };
 
      var map = new kakao.maps.Map(container, options);
    </script>
  </body>
</html>

위 코드를 jsx 문법에 맞게 작성해야 하는데요. 다양한 방법들이 존재하지만 <div>에 id 를 지정하는 대신에 useRef 를 이용했고, next의 <Script>를 사용하여 api를 호출했습니다.

'use client';
import { useEffect, useRef } from 'react';
import styles from './map.module.scss';
import Script from 'next/script';
 
export default function Map() {
  const mapRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const options = {
      //지도를 생성할 때 필요한 기본 옵션
      center: new kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
      level: 3, //지도의 레벨(확대, 축소 정도)
    };
 
    const map = new kakao.maps.Map(mapRef, options); //지도 생성 및 객체 리턴
  }, []);
 
  return (
    <>
      <Script
        src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_MAP_KEY}`}
      />
      <div ref={mapRef} className={styles.map}></div>;
    </>
  );
}

이렇게 작성하면 전역 객체에서 kakao를 찾을 수 없어 참조 에러가 발생합니다. 전역 객체에 kakao 타입을 추가한 후에 다음과 같이 코드를 수정했습니다.

declare global {
  interface Window {
    kakao: any;
  }
}

기존 kakao.maps에서 window.kakao.maps로 변경

useEffect(() => {
  const options = {
    //지도를 생성할 때 필요한 기본 옵션
    center: new window.kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
    level: 3, //지도의 레벨(확대, 축소 정도)
  };
 
  const map = new window.kakao.maps.Map(mapRef, options); //지도 생성 및 객체 리턴
}, []);

지도가 생성될 것 같지만 오류가 발생합니다.

이 오류는 스크립트보다 useEffect 코드가 먼저 실행되어 maps 프로퍼티를 찾을 수 없기 때문에 발생하는 오류입니다. 카카오 API는 스크립트를 동적으로 불러오기 위한 정적 메서드를 제공하고 있기에 코드를 수정하고 api key 뒤에 autoload=false를 추가했습니다.

<script
  type="text/javascript"
  src="http://dapi.kakao.com/v2/maps/sdk.js?autoload=false"
></script>
<script type="text/javascript">
  kakao.maps.load(function () {
    // v3가 모두 로드된 후, 이 콜백 함수가 실행됩니다.
    var map = new kakao.maps.Map(node, options);
  });
</script>

script 컴포넌트에 strategy 옵션에 beforeInteractive를 주어 hydration 이전에 실행시키게 만듭니다. beforeInteractive를 주게 되면 해당 스크립트를 루트 레이아웃으로 빼야 합니다. beforeInteractive를 설정한 스크립트는 루트 레이아웃 내에서만 작동한다고 공식 문서에서 설명하고 있습니다.

// layout.tsx
 
// ...
import Script from 'next/script';
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ko">
      <body className={inter.className}>
        <Script
          strategy="beforeInteractive"
          src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_MAP_KEY}&autoload=false`}
        />
        {children}
      </body>
    </html>
  );
}
//Map.tsx
 
'use client';
import { useEffect, useRef } from 'react';
import styles from './map.module.scss';
 
export default function Map() {
  const mapRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    window.kakao.maps.load(() => {
      const options = {
        //지도를 생성할 때 필요한 기본 옵션
        center: new window.kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
        level: 3, //지도의 레벨(확대, 축소 정도)
      };
 
      const map = new window.kakao.maps.Map(mapRef.current, options); //지도 생성 및 객체 리턴
    });
  }, []);
 
  return <div ref={mapRef} className={styles.map}></div>;
}

이제 카카오 맵이 정상적으로 생성됩니다. 이 방법 밖에도 document.createElement를 이용해 script를 생성하는 방법과 <Script> 컴포넌트에 onLoad prop을 이용해 하는 방법, 라이브러리를 이용한 방법 등 다양한 방법들이 있습니다.