Next.js+TS 환경에서 카카오 맵 API 쓰기

남주영·2022년 6월 12일
4

개발 기록

목록 보기
3/5
post-thumbnail

카카오 맵 API를 이용하기 위해서는 kakao developers에서 앱을 등록하고 인증 키를 발급 받아야 합니다.
그 외에도 해당 사이트에서 미리 해주어야 하는 일련의 절차가 있는데 이는 구글링을 하면 이미 좋은 자료들이 많으니 생략하겠습니다.

Next.js+TS에서 카카오 맵 API를 사용하기 위해서는 CRA+JS 환경과는 다르게 작성해야 하는 부분들이 있습니다.
이는 API를 로드하는 것kakao 객체 사용 방법인데 이 둘을 자세히 소개하겠습니다.

1. API 로드하기

카카오 맵 API는 라이브러리 같은 것이 아니기 때문에 직접 script 태그로 로드하여 사용해야 합니다.

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다."></script>

CRA에서는 위 코드를 index.html에 작성해주면 API가 로드되어 전역에서 kakao 객체를 사용할 수 있습니다.
kakao 객체를 통해 카카오 맵을 조작할 수 있습니다.

그런데 Next.js는 html 파일이 노출되어있지 않습니다.
그렇다면 어떻게 API를 로드할 수 있을까요?

2가지 방법이 있습니다!

(1) script 엘리먼트를 <head>에 직접 추가하기

createElement 메소드로 script 엘리먼트를 생성하여 내용을 담은 후,
appendChild 메소드를 이용해 직접 <head>에 script문을 추가할 수 있습니다.

const mapScript = document.createElement('script');

mapScript.async = true;
mapScript.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAOMAP_APPKEY}&autoload=false`;

document.head.appendChild(mapScript);

이 경우에는 load 이벤트를 활용해 api 로드가 완료된 후 kakao 객체를 이용할 수 있습니다.

Map.tsx

생략

export default function Map({ latitude, longitude }: MapProps) {
  useEffect(() => {
    const mapScript = document.createElement('script');
    mapScript.async = true;
    mapScript.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAOMAP_APPKEY}&autoload=false`;
    document.head.appendChild(mapScript);

    const onLoadKakaoMap = () => {
      window.kakao.maps.load(() => {
		생략
    };
    mapScript.addEventListener('load', onLoadKakaoMap);
  }, [latitude, longitude]);

  return (
    <>
      <div id="map" className="map-container" />
      <style jsx>{`
        .map-container {
          aspect-ratio: 320 / 220;
        }
      `}</style>
    </>
  );
}

(2) Next.js의 Script 컴포넌트 활용하기

Next.js에서 제공하는 Script 컴포넌트를 활용할 수 있습니다.
strategy prop에 beforeInteractive를 설정해주면 서버 초기 html에 삽입되어 페이지가 상호작용 하기 전에 script를 불러옵니다.

_app.tsx

생략
import Script from 'next/script';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script
        src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAOMAP_APPKEY}&autoload=false`}
        strategy="beforeInteractive"
      />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

Map.tsx

생략

export default function Map({ latitude, longitude }: MapProps) {
  useEffect(() => {
    window.kakao.maps.load(() => {
      생략
    });
  }, [latitude, longitude]);

  return (
    <>
      <div id="map" className="map-container" />
      <style jsx>{`
        .map-container {
          aspect-ratio: 320 / 220;
        }
      `}</style>
    </>
  );
}

2. kakao 객체 사용하기

위에 코드에서도 봤듯이 API를 로드한 후 kakao 객체를 이용하여 맵을 조작할 수 있습니다.

kakao 객체는 API가 로드되면 window 객체에 등록됩니다.
이는 어느 환경에서든 공통적인 사항이지만 JS/TS 또는 ESLint 사용 여부에 따라 kakao 객체를 사용하는 문법이 조금씩 달라집니다.
이를 소개해보겠습니다.

(1) Vanilla JS

기본적으로 자바스크립트는 선언하지 않고도 변수를 사용할 수 있습니다.
따라서 바닐라JS 환경에서는 script가 로드되어 windowkakao 객체가 등록됐는지 알 수 없는 정적인 상태에서도 kakao 객체를 바로 사용할 수 있습니다.

const container = document.getElementById('map');
const options = {
  center: new kakao.maps.LatLng(33.450701, 126.570667),
  level: 3,
};

const map = new kakao.maps.Map(container, options);

(2) ESLint+JS

(1)번처럼 바로 kakao 객체를 불러오는 방식은 ESLint의 no-undef 룰에 잡히게 됩니다.

아래와 같이 직접 window 객체에서 가져오게 되면 아무런 문제가 없지만 쓸데 없이 코드가 길어지게 됩니다.

  useEffect(() => {
    window.kakao.maps.load(() => {
      const container = document.getElementById('map');
      const options = {
        center: new window.kakao.maps.LatLng(latitude, longitude),
      };
      const map = new window.kakao.maps.Map(container, options);
      const markerPosition = new window.kakao.maps.LatLng(latitude, longitude);
      const marker = new window.kakao.maps.Marker({
        position: markerPosition,
      });
      marker.setMap(map);
    });
  }, [latitude, longitude]);

따라서 이를 회피할 수 있는 방법 두 가지를 소개해보도록 하겠습니다.

첫 번째는 no-undef 룰의 문서에서도 소개된 방법입니다.
바로 주석을 통해 kakao를 해당 파일 안에서 전역 변수로 만들어주는 것입니다.

/*global kakao*/
console.log(kakao);

ESLint는 config 파일뿐만 아니라 주석을 통해 설정을 해줄 수 있습니다. 자세한 것은 이 문서를 보면 알 수 있습니다. 또한, ESLint global에 대해 알고 싶다면 여기를 보면 됩니다.

두 번째는 window 객체에서 구조 분해 할당으로 kakao 객체를 가져오는 방법입니다.

const { kakao } = window;
console.log(kakao);

(3) TS

타입스크립트를 사용하면 Vanilla, CRA, Next.js 중 어떤 환경이든 관계 없이 아래와 같은 에러가 납니다.

이는 TS 에러이기 때문에 (2)에서 소개한 ESLint 관련인 첫번째 방법으로는 해결할 수 없습니다.

그리고 (2)처럼 window 객체에서 직접 불러올 수도 없으며,

(2)의 두 번째 방법인 구조 분해 할당도 소용이 없습니다.

그 이유는 타입스크립트에서는 window 객체도 타입이 있기 때문입니다.
그것이 Window & typeof globalThis 인데, 이 타입에 kakao라는 속성이 없기 때문에 에러가 나는 것입니다.

따라서 카카오 데브톡에서는 kakaoany 타입으로 만들어 사용하라고 합니다.

이는 두 가지 방법이 있습니다.

첫 번째 방법은 kakao를 아래와 같이 선언하여 쓰는 것입니다.
(any로 타입 추론됩니다.)

const kakao = (window as any).kakao;

두 번째 방법은 Window 타입을 확장하는 것입니다.

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

const { kakao } = window;
// CNA에서는 useEffect 안에서 해줘야 합니다

console.log(kakao);

이렇게 하면 Next.js + TS 환경에서 카카오 맵 API를 활용할 준비를 마친 것입니다! 👏👏

예시로 활용한 코드는 모두 정리해서 깃허브에 올려놓고 추가하러 오겠습니다! (Vanilla JS/TS, CRA JS/TS, Next.js TS)
추가) https://github.com/NamJwong/kakao-map-api-practice

저는 처음에는 any를 사용하고 싶지 않아 타입을 붙일 수 있는 방법을 찾아봤지만 일단 공식적으로는 타입을 제공하지 않기 때문에 불가능했습니다. 그러나 타입을 만들어 놓은 대단한 분이 있으시더군여.. 이곳에 가면 볼 수 있습니다. 그렇지만 활용해보는 것은 실패했습니다ㅠ 혹시나 누군가는 성공할 수도 있지 않을까 싶어 추가합니다!

profile
Sharing is Caring. 🪐

2개의 댓글

comment-user-thumbnail
2022년 6월 17일

https://react-kakao-maps-sdk.jaeseokim.dev/docs/intro#typescript
여기 링크에서 카카오 타입스크립트 타입 적용 가이드 확인할 수 있습니다~

1개의 답글