[Next.js14 프로젝트] 라이브러리 없이 카카오 API 사용하기

D uuu·2024년 5월 25일
0

Next.js14 프로젝트

목록 보기
4/11
post-thumbnail
post-custom-banner

Next.js 14 로 프로젝트를 진행하면서 카카오 지도와 로컬을 사용해야 했다.

공식 문서를 보니 정리도 잘 되어 있고 비교적 쉬운 난이도라 생각했는데, 생각보다 우여곡절이 많았다.

이유 중 하나가 next.js 14 관련 글이 적어 공식문서와 카카오 문서를 여러번 읽어가면서 하나씩 해보는 방법 밖엔 없었다...

그렇기에 나와 같은 분들에게 도움이 되고자 정보를 공유하고 더 나은 방법이 있거나 틀린 부분이 있다면 피드백을 받고자 작성한다.

우선, kakao developer 사이트에서 key 값을 받으면 script 태그를 선언해야 한다.

Script 태그로 선언하는 방식

카카오 공식 문서에 보면 스크립트 태그는 HTML 파일안의 head, body 등 어떠한 위치에 넣어도 상관없지만, 반드시 실행 코드보다 먼저 선언되어야 한다고 나와있다.

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

그래서 나는 <head> 태그 안에 script 태그를 선언해줬다. 그러면 스크립트가 실행 코드보다 먼저 로드되고 실행된다.

⭐️이때 모든 route 에서 third-party 스크립트를 로드하려면 Next.js 에서 제공하는 Script 태그를 사용해 root layout 에 추가해야 한다.

import Script from 'next/script';

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="ko">
            <head>
                <Script
                    type="text/javascript"
                    src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&libraries=services,clusterer,drawing&autoload=false`}              
                />
            </head>
            <body style={{ display: 'flex' }}>
                <ReactQueryProvider>
                    <StyledComponent>
                         {children}             
                    </StyledComponent>
                </ReactQueryProvider>
            </body>
        </html>
    );
}

이렇게 카카오 API SDK 를 로드 하고 나서 실제로 지도를 불러오기 위한 코드를 작성해줘야 한다.

const mapEl = useRef<HTMLDivElement>(null);

useEffect(() => {
 if (!mapEl.current) return;
 
	const options = {
    	center : new kakao.maps.LatLng(latitude, longitude)
        level : 3
     }
     const map = new kakao.maps.Map(mapEl.current!, option)
},[])

 return <div ref={mapEl} style={{ width: '100%', height: '100%' }}></div>;

이처럼 script 를 먼저 선언하고, 지도를 불러오는 로직을 공식문서에 나와 있는 대로 작성했는데, 아래와 같은 에러가 나온다.

Cannot read properties of undefined (reading 'maps')

해당 오류는 주로 script 가 로드되기 전에 객체에 접근해서 생기는 오류이다. 즉, script 가 로드되기 전에 useEffect 훅이 먼저 실행되어서 maps 객체를 찾을 수 없어 에러가 발생하는 것이다.

혹시나 스크립트를 잘 못 작성한걸까 싶어서 공식문서를 다시 살펴보니 Script 에는 전략 속성을 사용해 로드 동작을 조정할 수 있었다.

afterInteractive 는 기본값 (default) 이고, 읽어보니 beforeInteractive 가 Next.js 코드 및 hydration 이 되기 전에 스크립트가 로드되는 속성이므로 strategy 를 추가해줬다.

<head>
  <Script
     type="text/javascript"
     src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&libraries=services,clusterer,drawing&autoload=false`}              
     strategy="beforeInteractive"
   />
 </head>

그러나 여전히 같은 에러가 발생했다. 혹시나 싶어 strategy 를 afterInteractive, beforeInteractive, lazyOnload 로 변경해봐도 똑같은 에러가 발생했다.

kakao.maps.load 사용하기

해결방법을 찾다가 kakao dev 에서 관리자(?) 분이 작성한 글을 발견했다.

⭐️스크립트 동적 로딩(autoload=false )을 사용한 경우에는 내가 필요한 시점에 스크립트를 불러와서 실행을 하게 되므로 이 시점을 알려주는 kakao.maps.load 함수를 사용해야 한다는 것이다..!

문서를 찾아보니 스크립트의 로딩이 끝나기 전에 객체에 접근하려고 하면 에러가 발생하기 때문에 = 내가 겪은 에러

로딩이 끝나는 시점에 콜백함수를 통해 객체에 접근할 수 있도록 load 를 사용한다고 되어 있다.

kakao.maps.load 함수를 사용하기 위해서 kakao 객체에 접근해야 하는데 script 에 이미 kakao api 를 설치했기 때문에 window 객체 안에 kakao 가 들어가 있다.

따라서 window 에서 kakao 객체를 뽑아서 사용할 수 있다.

const mapEl = useRef<HTMLDivElement>(null);

useEffect(() => {
 const {kakao} = window;
 if(!kakao) return;
 
 kakao.maps.load(() => {
     if (!mapEl.current) return;
      const center = new kakao.maps.LatLng(latitude, longitude);
      const options = {
         center,
         level: 3,
       };
       
      const map = new kakao.maps.Map(mapEl.current!, options);
   });
},[])

 return <div ref={mapEl} style={{ width: '100%', height: '100%' }}></div>;

전체 코드

app/layout.tsx

import Script from 'next/script';

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="ko">
            <head>
                <Script
                    type="text/javascript"
                    src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&libraries=services,clusterer,drawing&autoload=false`}              
                />
            </head>
            <body style={{ display: 'flex' }}>
                <ReactQueryProvider>
                    <StyledComponent>
                         {children}             
                    </StyledComponent>
                </ReactQueryProvider>
            </body>
        </html>
    );
}
components/Map.tsx

const Map = () => {
  const mapEl = useRef<HTMLDivElement>(null);

  useEffect(() => {
   const {kakao} = window;
   if(!kakao) return;
 
   kakao.maps.load(() => {
     if (!mapEl.current) return;
      const center = new kakao.maps.LatLng(latitude, longitude);
      const options = {
         center,
         level: 3,
       };
       
      const map = new kakao.maps.Map(mapEl.current!, options);
   });
},[])

  return <div ref={mapEl} style={{ width: '100%', height: '100%' }}></div>;
 
 }

useEffect 훅을 이용해 직접 script 추가하기

혹시나 위 방법으로도 되지 않거나 필요할때 동적으로 스크립트를 생성해 지도를 불러오고 싶다면 아래와 같은 방법을 사용해도 된다.

⭐️우선 직접 script 를 생성할 것이므로 app/layout.tsx 에 Script 태그 부분은 삭제한다.

그 다음 useEffect 훅을 이용해 동적으로 script 를 생성하는 코드를 작성해준다.

components/Map.tsx

useEffect(() => {
  const script = document.createElement('script');
  script.async = false;
  script.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&libraries=services,clusterer,drawing&autoload=false`;
  document.head.appendChild(script);

  script.onload = () => {
     kakao.maps.load(() => {
        if (!mapEl.current) return;
     
        const center = new kakao.maps.LatLng(latitude, longitude);
        const options = {
            center,
            level: 3,
         };
        const map = new kakao.maps.Map(mapEl.current!, options);       
       });
     };
    }, []);

document.createElement 이용해 script 태그를 직접 만들어 준다.

그걸 async 방식으로 만들어 준 다음 script.src 에 카카오 api 주소를 넣어준다.

그리고 head 부분에 해당 script 를 붙여주면 앞서 Script 태그로 작성한 것 처럼 생성 된다.

script.onload 함수를 통해 스트립트가 로드된 후에 특정 함수를 실행하도록 설정한다. 여기서는 지도를 불러오는 로직을 작성했다.

정리하기

Next.js 에서 (14버전) 카카오 API 를 사용하는 방법을 정리하면 아래와 같다.

Script 태그 사용하기

Next.js(14ver) app/layout.tsx 파일에서 head 부분에 Script 태그 선언한다.

<head>
  <Script
      type="text/javascript"
       src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&libraries=services,clusterer,drawing&autoload=false`}
   />
</head> 
<body>
...생략
</body>

useEffect 훅을 사용해 Kakao Maps API가 로드된 후에 지도를 초기화하는 로직을 작성한다.

useEffect(() => {
 const { kakao } = window;
 if(!kakao) return;
 
 kakao.maps.load(() => {
 
  /// 지도 불러오는 로직  
  
  }) 
 },[])

위 방법은 언제 쓰면 좋을까 ??

  • script 가 페이지 렌더링 전에 로드 되므로 모든 컴포넌트에서 바로 API 를 사용할 수 있다.

  • script 로직이 layout.tsx 파일에 작성되어 있어 컴포넌트에서 별도로 스크립트 로직을 작성하지 않아도 된다. 코드가 분리되어 깔끔하다.

  • 모든 페이지에서 script 를 로드하기 때문에 필요 없는 페이지에서도 불필요한 로드가 발생할 수 있다.

  • 특정 컴포넌트에서만 API 를 사용할때는 위 방법은 불필요한 로드가 발생하고 따라서 로드 시간이 증가되는 문제가 생길 수 있다.


useEffect 활용한 동적으로 script 생성하기

app/layout.tsx 파일에서 head 부분에 Script 태그를 삭제한다.

동적으로 script 를 생성한 후, 로드 되었을때 지도를 초기화하는 로직을 작성한다.

useEffect(() => {
  const script = document.createElement('script');
  script.async = false;
  script.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&libraries=services,clusterer,drawing&autoload=false`;
  document.head.appendChild(script);

  script.onload = () => {
     kakao.maps.load(() => {
     
          /// 지도 불러오는 로직  
       });
     };
    }, []);

위 방법은 언제 쓰면 좋을까 ??

  • 특정 컴포넌트에서만 script 를 로드할 수 있어 필요한 경우에만 로드할 수 있다.

  • 단, 필요한 컴포넌트마다 script 를 로드하는 코드를 작성해야 한다.

마무리

나는 지도가 서비스 전반에 사용되어야 하므로 첫번째 방법인 head 부분에 script 를 삽입해 사용했다.

profile
배우고 느낀 걸 기록하는 공간
post-custom-banner

0개의 댓글