카카오 맵 API를 이용하기 위해서는 kakao developers에서 앱을 등록하고 인증 키를 발급 받아야 합니다.
그 외에도 해당 사이트에서 미리 해주어야 하는 일련의 절차가 있는데 이는 구글링을 하면 이미 좋은 자료들이 많으니 생략하겠습니다.
Next.js+TS에서 카카오 맵 API를 사용하기 위해서는 CRA+JS 환경과는 다르게 작성해야 하는 부분들이 있습니다.
이는 API를 로드하는 것과 kakao
객체 사용 방법인데 이 둘을 자세히 소개하겠습니다.
카카오 맵 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가지 방법이 있습니다!
<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>
</>
);
}
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>
</>
);
}
위에 코드에서도 봤듯이 API를 로드한 후 kakao
객체를 이용하여 맵을 조작할 수 있습니다.
이 kakao
객체는 API가 로드되면 window 객체에 등록됩니다.
이는 어느 환경에서든 공통적인 사항이지만 JS/TS 또는 ESLint 사용 여부에 따라 kakao
객체를 사용하는 문법이 조금씩 달라집니다.
이를 소개해보겠습니다.
기본적으로 자바스크립트는 선언하지 않고도 변수를 사용할 수 있습니다.
따라서 바닐라JS 환경에서는 script가 로드되어 window
에 kakao
객체가 등록됐는지 알 수 없는 정적인 상태에서도 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);
(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);
타입스크립트를 사용하면 Vanilla, CRA, Next.js 중 어떤 환경이든 관계 없이 아래와 같은 에러가 납니다.
이는 TS 에러이기 때문에 (2)에서 소개한 ESLint 관련인 첫번째 방법으로는 해결할 수 없습니다.
그리고 (2)처럼 window 객체에서 직접 불러올 수도 없으며,
(2)의 두 번째 방법인 구조 분해 할당도 소용이 없습니다.
그 이유는 타입스크립트에서는 window 객체도 타입이 있기 때문입니다.
그것이 Window & typeof globalThis
인데, 이 타입에 kakao
라는 속성이 없기 때문에 에러가 나는 것입니다.
따라서 카카오 데브톡에서는 kakao
를 any
타입으로 만들어 사용하라고 합니다.
이는 두 가지 방법이 있습니다.
첫 번째 방법은 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
를 사용하고 싶지 않아 타입을 붙일 수 있는 방법을 찾아봤지만 일단 공식적으로는 타입을 제공하지 않기 때문에 불가능했습니다. 그러나 타입을 만들어 놓은 대단한 분이 있으시더군여.. 이곳에 가면 볼 수 있습니다. 그렇지만 활용해보는 것은 실패했습니다ㅠ 혹시나 누군가는 성공할 수도 있지 않을까 싶어 추가합니다!
https://react-kakao-maps-sdk.jaeseokim.dev/docs/intro#typescript
여기 링크에서 카카오 타입스크립트 타입 적용 가이드 확인할 수 있습니다~