지도 개발, 이제 더 빠르고 쉽게: react kakao map

사이드 프로젝트를 지도 기반 서비스로 하였다보니, naver map과 kakao map 간에 고민이 많았는데, Next에서 좀 더 사용하기 쉽게 설정되어 있는 react-kakao-map 라이브러리를 사용하여 프로젝트를 만들어 보았습니다.
Nov 22, 2023
지도 개발, 이제 더 빠르고 쉽게: react kakao map
이번 글에서는 react-kakao-map을 next.js 기반에서 사용하는 법과 사용하면서 터득한 몇가지 꿀팁들을 공유해보도록 하겠습니다.
 

Next.js에서 React kakao map 사용하기

아래 모든 예시는 Next 13 App Router를 기반으로 합니다. Next 12 version의 경우, react-kakao-map 에 있는 튜토리얼을 참고하시길 바랍니다.

Next.js 설치하기

npx create-next-app@latest

SDK 불러오기

// app/layout.tsx import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import Script from "next/script"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="ko"> <Script src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&libraries=services,clusterer&autoload=false`} strategy="beforeInteractive" /> <body className={inter.className}>{children}</body> </html> ); }
layout.tsx 에 Script를 통해서 kakao Map SDK를 불러옵니다.
저같은 경우, 다른 페이지에서도 카카오 맵을 사용해야 했기 때문에, root layout에 SDK를 불러오는 Script를 위치 시켰습니다. 특정 페이지에서만 지도를 사용하신다면, 용도에 따라 <Script> 파일의 위치를 조정하면 될 것 같습니다.
kakao App Key의 경우, 링크를 통해서 발급 받으실 수 있습니다.

타입스크립트 타입 추가하기

npm install kakao.maps.d.ts --save-dev
// tsconfig.json { ..., "compilerOptions": { ..., "types": [ ..., "kakao.maps.d.ts" ] } }
카카오 SDK에서 공식 지원하는 타입을 추가하려면, 타입을 설치하고, 위와 같이 tsconfig.json 파일에 kakao.maps.d.ts 타입을 명시해 줍니다.

패키지 설치하기

npm install react-kakao-maps-sdk
이렇게 설치가 완료되었고, 이제부터는 본격적으로 자주 쓰는 기능들을 알아보도록 하겠습니다
 
 

지도로 전체 화면 채우기

"use client"; import { Map } from "react-kakao-maps-sdk"; export default function Home() { return ( <Map center={{ lat: 33.5563, lng: 126.79581 }} style={{ width: "100%", height: "100vh" }} ></Map> ); }
저는 현재 보고 있는 화면의 브라우저 창의 높이를 표현하는 100vh 를 통해 화면을 꽉 채워서 사용하고 있습니다.

지도에 마커 만들기

"use client"; import { Map, MapMarker } from "react-kakao-maps-sdk"; export default function Home() { return ( <Map center={{ lat: 33.5563, lng: 126.79581 }} style={{ width: "100%", height: "100vh" }} > <MapMarker position={{ lat: 33.55635, lng: 126.795841 }} /> </Map> ); }
MapMaker 컴포넌트의 props에 원하는 마커의 경도와 위도를 제공하면 아래와 같이 Map 상에 마커를 생성할 수 있습니다.
notion image
추가로 여러개의 마커를 생성하려면 아래와 같이 작성하면 됩니다.
"use client"; import { Map, MapMarker } from "react-kakao-maps-sdk"; const LOCATIONS = [ { lat: 33.55635, lng: 126.795841, id: 1 }, { lat: 33.55621, lng: 126.795841, id: 2 }, ]; export default function Home() { return ( <Map center={{ lat: 33.5563, lng: 126.79581 }} style={{ width: "100%", height: "100vh" }} > {LOCATIONS.map((location) => ( <MapMarker position={location} key={location.id} /> ))} </Map> ); }
notion image
위와 같이 마커가 추가된 것을 알 수 있습니다.

나만의 마커 아이콘 생성하기

CustomOverlayMap 는 제가 원하는 위치에 자식 컴포넌트를 렌더링 시킬 수 있는 컴포넌트입니다.
"use client"; import { MapPin } from "lucide-react"; import { CustomOverlayMap, Map } from "react-kakao-maps-sdk"; const LOCATIONS = [ { lat: 33.55635, lng: 126.795841, id: 1 }, { lat: 33.55621, lng: 126.795841, id: 2 }, ]; export default function Home() { return ( <Map center={{ lat: 33.5563, lng: 126.79581 }} style={{ width: "100%", height: "100vh" }} > <CustomOverlayMap position={{ lat: 33.55635, lng: 126.795841 }}> <MapPin size={48} color="#27D51B" /> </CustomOverlayMap> </Map> ); }
저는 위와 같이, lucide-react에서 icon을 가져와 원하는 위치에 렌더링 시켰습니다.
notion image
위와 같이 Pin Icon이 정확하게 원하는 위치에 노출된 것을 알 수 있습니다.

현재위치 다른 사람과 공유하기

제가 했던 프로젝트의 경우, 특정 위치를 사람들과 공유해야 하는 경우들이 있었습니다. 이럴 때는 어떻게 해야 하는지 살펴보도록 하겠습니다. url로 정확한 위치를 사람들과 공유하려면 url에 지도에 표시하고자 위치를 query parameter 형태로 보관해야 합니다.
"use client"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Map } from "react-kakao-maps-sdk"; export default function Home() { const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); // query-parameter에 위도 경도가 있으면, 추가하고 없으면 default Position을 셋팅합니다. const position = { lat: Number(searchParams.get("lat")) || 33.5563, lng: Number(searchParams.get("lng")) || 126.79581, }; // 지도의 드래그가 끝날 때마다 포지션을 query-parameter에 저장합니다. const handlePosition = (map: kakao.maps.Map) => { const lng = map.getCenter().getLng(); const lat = map.getCenter().getLat(); const params = new URLSearchParams(searchParams); params.set("lng", String(lng)); params.set("lat", String(lat)); router.replace(`${pathname}?${params.toString()}`); }; return ( <Map center={position} onDragEnd={handlePosition} style={{ width: "100%", height: "100vh" }} ></Map> ); }
저는 위와 같이 드래그가 끝났을 때, position을 url query parameter에 기록하는 handlePosition 함수를 만들었습니다. 또한 url query-parameter를 통해서 입력된 위도와 경도를 position 객체를 통해 동기화 해주었습니다.
이를 통해 마지막으로 드래그된 지도의 현재위치를 다른 사람들과 공유할 수 있습니다.
notion image
제 사이드 프로젝트의 경우에는 아래와 같이 모달 창까지 오픈하여 보여주도록 구현하였습니다

현재 위치로 이동하기

브라우저에서 제공하는 location API를 통해 현재 위치로 이동하는 방법을 알아보도록 하겠습니다.
"use client"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Map } from "react-kakao-maps-sdk"; export default function Home() { ... return ( <> <div className="fixed left-0 right-0 top-0 z-30 px-3 pt-3 z-index-10 h-4"> <button className="bg-white border border-gray-300 rounded-md shadow-sm py-1 px-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> Current Location </button> </div> <Map center={position} onDragEnd={handlePosition} style={{ width: "100%", height: "100vh" }} ></Map> </> ) ... }
지도 상단에 버튼을 하나 추가하도록 하겠습니다
notion image
그리고 핸들러에서 location API에서 현재 위치 정보를 가져오navigator.geolocation.getCurrentPosition 함수를 호출합니다.
"use client"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Map } from "react-kakao-maps-sdk"; export default function Home() { ... return ( <> <div className="fixed left-0 right-0 top-0 z-30 px-3 pt-3 z-index-10 h-4"> <button className="bg-white border border-gray-300 rounded-md shadow-sm py-1 px-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" onClick={() => { navigator.geolocation.getCurrentPosition( // 현재 위치를 받는 것에 성공하면 실행되는 success callback (position) => { router.replace( `/?${new URLSearchParams({ lat: String(position.coords.latitude), lng: String(position.coords.longitude), }).toString()}` ); }, // 현재 위치를 받는 것에 실패했을 때, 실행하는 failure callback () => alert("위치 정보를 가져오는데 실패했습니다."), // location을 가져오는 것에 사용되는 각종 옵션 { enableHighAccuracy: true, maximumAge: 30000, timeout: 27000, } ); }} > Current Location </button> </div> <Map center={position} onDragEnd={handlePosition} style={{ width: "100%", height: "100vh" }} ></Map> </> ); }
getCurrentPosition 를 호출하여 현재 위치를 가져오는데 성공했다면, 위 현재 위치로 이동하기와 마찬가지로 경도와 위도를 url에 query parameter에 추가해 줍니다. url이 변경됨에 따라 전체 컴포넌트가 re-render되게 되고 얻어진 현재 위치로 지도가 이동하는 것을 알 수 있습니다
// 전체 코드 "use client"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Map } from "react-kakao-maps-sdk"; export default function Home() { const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); const position = { lat: Number(searchParams.get("lat")) || 33.5563, lng: Number(searchParams.get("lng")) || 126.79581, }; const handlePosition = (map: kakao.maps.Map) => { const lng = map.getCenter().getLng(); const lat = map.getCenter().getLat(); const params = new URLSearchParams(searchParams); params.set("lng", String(lng)); params.set("lat", String(lat)); router.replace(`${pathname}?${params.toString()}`); }; return ( <> <div className="fixed left-0 right-0 top-0 z-30 px-3 pt-3 z-index-10 h-4"> <button className="bg-white border border-gray-300 rounded-md shadow-sm py-1 px-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" onClick={() => { navigator.geolocation.getCurrentPosition( (position) => { router.replace( `/?${new URLSearchParams({ lat: String(position.coords.latitude), lng: String(position.coords.longitude), }).toString()}` ); }, () => alert("위치 정보를 가져오는데 실패했습니다."), { enableHighAccuracy: true, maximumAge: 30000, timeout: 27000, } ); }} > Current Location </button> </div> <Map center={position} onDragEnd={handlePosition} style={{ width: "100%", height: "100vh" }} ></Map> </> ); }
 

결론

장점

  • 라이브러리가 편해서 쉽게 지도를 개발할 수 있었다
  • naver-react-map 보다 react기반 환경에서 개발하기 편하다.

단점

  • 네이버 지도 대비해서 카카오 지도가 내용이 조금 부실하다
  • 레퍼런스가 많이 없어서 이래저래 많이 찾아봐야 한다.
 

레퍼런스

 
Share article

More articles

See more posts

indietools