현재 진행하고 있는 프로젝트에 카카오맵을 연동하게 되었다.
그런데 찾아보다보니, 카카오맵 공식 홈페이지에서 보여주고 있는 예시는 ES5 문법으로 된 바닐라 자바스크립트로 되어 있는 것 같았다.
이걸 넥스트와 타입스크립트로 적용하는 과정에서 다양한 블로그와 글들을 참고했고 넥스트+타입스크립트 환경에서 성공적으로 적용하는 법을 알아냈다.
카카오맵 API 는 카카오맵 API 사이트 로 접속해서 쓸 수 있다
그러나 그 전에 카카오맵 API 를 사용하려면 카카오 개발자 사이트 로 이동해서 내 애플리케이션을 먼저 등록하고 API key 를 발급받아야 한다.
그럼 이렇게 key 들을 발급받을 수 있는데 여기서는 JavaScript key 를 사용하면 되고, 사이트 도메인을 등록해주면 된다.
위에서 발급받은 Javascript key 를 복사해서 프로젝트에 환경변수로 등록해준다.
// .env.local
NEXT_PUBLIC_KAKAOMAP_APPKEY="JavaScript key를 여기다가 붙입니다."
넥스트 프로젝트에서는 .env.local
파일에다가 환경변수를 등록해준다.
NEXT_PUBLIC_KAKAOMAP_APPKEY
뒤에다가 api key 를 붙여넣어준다. 여기에 따옴표로 감싸주어야 한다.
본격적으로 카카오맵 api 를 등록하기 위해선 JavaScript 의 <script>
를 붙여넣어주어야 한다.
그런데 Next 에서는 따로 <script>
를 붙인 곳이 없어보인다.
React 의 경우에는 index.html 이 있기 때문에 거기서 script 태그를 붙이면 되지만 넥스트에서는 어디에다가 붙여야 할까
// _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<script src={ `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAOMAP_APPKEY}&autoload=false&libraries=services`}></script>
</Head>
<ThemeProvider theme={themeOptions}>
<GlobalStyles />
<AppLayout>
<Component {...pageProps} />
</AppLayout>
</ThemeProvider>
</>
)
}
나의 경우는 이렇게 _app.tsx
파일에서 <Head>
태그 안에다가 <script>
태그를 붙여넣어줬다.
src 에서 appkey 뒤에는 아까 .env.local
에 등록한 환경변수 값을 꺼내오면 된다.
그러나 이 경우 넥스트 자체에서 빨간 밑줄로 에러를 내보내게 되는데 해당 문구는 아래와 같다.
External synchronous scripts are forbidden.
외부의 스크립트는 금지된다는 뜻인 것 같다.
이 문제를 해결하기 위해서 아래와 같이 코드를 수정했다.
// _app.tsx
import Script from 'next/script';
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Script
strategy='beforeInteractive'
src={ `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAOMAP_APPKEY}&autoload=false&libraries=services`}
/>
<ThemeProvider theme={themeOptions}>
<GlobalStyles />
<AppLayout>
<Component {...pageProps} />
</AppLayout>
</ThemeProvider>
</>
)
}
외부 스크립트를 사용하기 위해서 'next/sript'
에서 Script
를 import 한 뒤 해당 <Script>
를 기존의 <script>
처럼 사용하면 된다.
'next/script'
를 사용하면 strategy
속성을 정의할 수 있고 Next.js 가 스크립트 로딩을 최적화해준다
beforeInteractive
: 봇이 감지하거나 동의 관리처럼 그 페이지가 상호작용하기 전에 가져오고 실행되어야 할 필요가 있는 중요한 스크립트의 경우 사용한다afterInteractive
: (디폴트) 태그 매니저나 분석툴 처럼 해당 페이지의 상호작용 이후에 가져오고 실행되는 스크립트의 경우 사용한다lazyOnload
: 고객지원 챗이나 SNS 위젯처럼 유휴 시간동안 로드를 기다릴 수 있는 스크립트의 경우 사용한다.또한 카카오 공식문서에 따르면, 스크립트의 로딩이 끝나기 전에 v3 의 객체에 접근하려고 하면 에러가 발생하기 때문에
로딩이 끝나는 시점에 콜백을 통해 객체에 접근하기 위해서 window.kakao.maps.load
를 사용하고,
이때 v3 로딩 스크립트 주소에 파라미터로 autoload=false
를 지정해주어야 한다고 한다..
가장 마지막 libraries=services
는 주소를 좌표로 변환해서 사용하기 위해 추가한 라이브러리다.
import { FC, useEffect } from "react";
import styled from "@emotion/styled";
const MapContainer = styled.div`
aspect-ratio: 16 / 9;
`
declare global {
interface Window {
kakao: any;
}
}
const Map: FC<{address: string}> = ({ address }) => {
useEffect(() => {
const onLoadKakaoMap = () => {
window.kakao.maps.load(() => {
const geocoder = new window.kakao.maps.services.Geocoder() // 주소-좌표 반환 객체를 생성
// 주소로 좌표를 검색
geocoder.addressSearch(address, (result: any, status: any) => {
if (status === window.kakao.maps.services.Status.OK) { // 정상적으로 검색이 완료됐으면
var coords = new window.kakao.maps.LatLng(result[0].y, result[0].x)
// 지도를 생성
const container = document.getElementById("map")
const options = {
center: coords,
level: 3,
}
const map = new window.kakao.maps.Map(container, options)
// 결과값으로 받은 위치를 마커로 표시
new window.kakao.maps.Marker({
map: map,
position: coords,
})
} else { // 정상적으로 좌표가 검색이 안 될 경우 디폴트 좌표로 검색
const container = document.getElementById("map")
const options = {
center: new window.kakao.maps.LatLng(33.450701, 126.570667),
level: 3,
}
// 지도를 생성
const map = new window.kakao.maps.Map(container, options)
new window.kakao.maps.Marker({
map: map,
position: coords,
})
}
})
})
}
onLoadKakaoMap()
}, [address])
return (
<MapContainer id="map" />
)
}
export default Map
먼저 window.kakao
를 사용하기 위해 global interface 를 선언한다
위치는 어디여도 상관없다.
나는 주소를 좌표로 변환해서 사용할 것이기 때문에 인터넷에 보이는 것처럼 위도와 경도를 받지 않고 오직 한글 주소를 props 로 받도록 해놓았다.
onLoadKakaoMap
함수에서 window.kakao.maps.load
가 되면 실행되도록 했는데,
먼저 주소-좌표 반환 객체를 생성한 뒤 props 로 받은 주소가 좌표로 정상적으로 변환이 되면 지도를 생성, 마커로 표시하였고, 그게 아니면 기본 좌표를 등록하도록 했다.
(기본 좌표는 제주도 카카오 회사..)
이제 사용하는 곳에선 아래와 같이 사용하면 된다
import Map from "parts/Map"
<Map address={address} />
나중엔 이런식으로 props 로 받은 주소를 자동으로 변환하도록 했다.
address 에는 주소로 인식할 수 있는 문자열이 들어오면 된다.
그러면 이렇게 화면에 지도가 아주아주 예쁘게 잘 나타난다.