김재욱의 이모저모

Naver Maps Api를 활용하기 (+ next.js 에서 cors 오류 해결하기!) 본문

Front-end

Naver Maps Api를 활용하기 (+ next.js 에서 cors 오류 해결하기!)

kjyook 2025. 9. 20. 20:41
728x90

Naver Maps API로 만드는 나만의 지도 서비스! (feat. Next.js & TypeScript)

새로운 프로젝트에 참여하면서 naver maps api를 활용할 일이 생겼고, 이 과정에서 새로운 api를 활용해서 웹 서비스에 원하는 기능이 들어간 지도를 구현한 경험과 그 과정에서 있었던 문제점과 해결점을 기록해두려고 한다.


1. Naver Maps API 기능

Naver Maps API는 다양한 기능을 제공하여 다채로운 지도 서비스를 만들 수 있게 해준다. 여러가지 있지만 내가 사용한 기능들은 아래와 같다.

  • Map: 지도의 기본이 되는 기능입니다. 원하는 위치를 중심으로 지도를 표시하고 확대/축소, 패닝 등을 제어할 수 있습니다.
  • Reverse Geocode: 좌표(위도, 경도)를 주소로 변환하는 기능입니다. GPS로 얻은 위치를 사용자에게 친숙한 주소 정보로 보여줄 때 유용합니다.
  • Directions: 출발지와 목적지 정보를 바탕으로 최적의 길 찾기 경로를 제공합니다. 도보, 자동차 등 다양한 교통수단 옵션을 설정할 수 있습니다.
  • Custom Marker: 기본 마커 대신 원하는 이미지나 아이콘으로 마커를 꾸밀 수 있습니다. 사용자의 현재 위치나 특정 장소를 시각적으로 강조할 때 효과적입니다.
  • Polyline: 지도 위에 선(line)을 그리는 기능입니다. 사용자의 이동 경로를 추적하여 표시하거나, 특정 구간을 강조할 때 사용됩니다.

이러한 기능들을 조합하면 단순한 위치 기반 서비스뿐만 아니라, 운동 경로 기록, 맛집 투어 지도 등 다양한 아이디어를 현실로 만들 수 있습니다.


2. Next.js 기반 프로젝트에서 Naver Maps 활용법

Map

Next.js 환경에서 Naver Maps API를 사용하는 가장 일반적인 방법은 <Script> 컴포넌트를 활용하는 것입니다.

'use client';

import Script from 'next/script';
import { useEffect, useRef } from 'react';

const MyMapComponent = () => {
  const mapRef = useRef<naver.maps.Map | null>(null);

  const initializeMap = () => {
    // 지도 초기화 및 마커, 폴리라인 등 생성 로직
    mapRef.current = new naver.maps.Map('map-container', {
      center: new naver.maps.LatLng(37.3595704, 127.105399),
      zoom: 17,
    });
    // ... 추가적인 지도 기능 구현
  };

  return (
    <>
      <Script
        type="text/javascript"
        strategy="afterInteractive"
        src={`https://oapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_NAVER_CLIENT_ID}&submodules=drawing`}
        onLoad={initializeMap}
      />
      <div id="map-container" className="w-full h-full" />
    </>
  );
};
  • 'use client': React Hooks와 DOM 조작을 위해 클라이언트 컴포넌트로 지정해야 합니다.
  • <Script>: Next.js의 <Script> 컴포넌트를 사용하면 지도 스크립트 로딩을 효율적으로 관리할 수 있습니다. strategy="afterInteractive"는 페이지가 상호작용 가능해진 후 스크립트를 로드하라는 의미입니다.
  • onLoad: 스크립트가 성공적으로 로드된 후 initializeMap 함수가 호출되어 지도 객체를 생성합니다.
  • 환경 변수: process.env.NEXT_PUBLIC_NAVER_CLIENT_ID를 사용하여 클라이언트 ID를 안전하게 관리합니다. NEXT_PUBLIC_ 접두사를 사용해야 클라이언트 측 코드에서 접근할 수 있습니다.

reverse geocode

/api/geocode/route.ts 에서 api 호출 코드

import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  const query = req.nextUrl.searchParams.toString();
  const api_url = `https://maps.apigw.ntruss.com/map-reversegeocode/v2/gc?${query}`;

  const header = {
    'x-ncp-apigw-api-key-id': process.env.NEXT_PUBLIC_NAVER_CLIENT_ID || '',
    'x-ncp-apigw-api-key': process.env.NAVER_CLIENT_SECRET || '',
  };

  try {
    const response = await fetch(api_url, {
      method: 'GET',
      headers: header,
    });

    if (!response.ok) {
      const errorData = await response.json();
      return NextResponse.json(errorData, { status: response.status });
    }

    const data = await response.json();
    return NextResponse.json(data);
  } catch (err) {
    console.error('api router error:', err);
    return NextResponse.json(
      { error: '서버 오류가 발생했습니다.' },
      { status: 500 }
    );
  }
}

사용할 컴포넌트 or 페이지에서 해당 api 호출하고 데이터 사용하기

const [geocode, setGeocode] = useState<{ lat: number; lng: number } | null>(
    null
  );

//처음 진입시 사용자 위치 받아서 header에 뿌려주기
  useEffect(() => {
    navigator.geolocation.getCurrentPosition(
      (position) => {
        setGeocode({
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        });
        console.log('now location geocode', geocode);
      },
      (err) => {
        console.error(err);
      }
    );
  }, []);

//처음에 load한 위치 오면 reverse geocoding api로 주소 받아오기
useEffect(() => {
    console.log('geocode changed', geocode);
    const response = async () => {
      console.log('fetch location data');
      if (geocode) {
        const res = await fetch(
          `/api/geocode?coords=${geocode.lng},${geocode.lat}&output=json`
        );
        const data = await res.json();
        console.log(data);

        if (data) {
          setLocation(
            data.results[0].region.area2.name +
              ' ' +
              data.results[0].region.area3.name
          );

          console.log(location);
        }
        return;
      }
    };
    response();
  }, [geocode]);

네이버 공식 문서


custom marker

interface MarkerProps {
  position: naver.maps.LatLng;
  map: naver.maps.Map;
}

const Marker = ({ position, map }: MarkerProps) => {
  const _marker = new naver.maps.Marker({
    position: position,
    map: map,
    //https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Marker.html#~ImageIcon icon props 설명 문서
    icon: {
      url: './public/icon/user_gps.png',
      size: new naver.maps.Size(47, 47),
      origin: new naver.maps.Point(0, 0),
      anchor: new naver.maps.Point(23.5, 23.5),
    },
  });

  return _marker;
};

필수 옵션은 position, map이지만 icon에 원하는 이미지, size를 설정하여 custom이 가능하고 origin, anchor는 초점을 맞추는 옵션이다.

3. 트러블 슈팅: CORS 오류와 Next.js API Routes 활용

Naver Maps API를 사용하다 보면 Reverse GeocodeDirections와 같이 서버 간 통신이 필요한 기능에서 CORS (Cross-Origin Resource Sharing) 오류를 마주칠 수 있습니다. 이는 브라우저가 클라이언트(프론트엔드)에서 다른 도메인의 서버(Naver Cloud 서버)로 직접 요청하는 것을 보안상 막기 때문입니다.

🤔 해결책: Next.js API Routes

이 문제를 해결하기 위해 Next.js의 API Routes를 활용할 수 있습니다. API Routes는 Next.js 애플리케이션 내부에 서버리스 함수를 만들어주는 기능으로, 프록시(Proxy) 서버처럼 작동하게 만들 수 있습니다.

✅ 해결 과정

  1. pages/api/ 또는 app/api/ 경로에 API Route 파일 생성:
  2. // app/api/naver-api/route.ts import { NextResponse } from 'next/server'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const query = searchParams.toString(); const response = await fetch(`https://openapi.naver.com/v1/map/geocode.json?${query}`, { headers: { 'X-NCP-APIGW-API-KEY-ID': process.env.NAVER_MAPS_API_KEY_ID!, 'X-NCP-APIGW-API-KEY': process.env.NAVER_MAPS_API_KEY!, }, }); const data = await response.json(); return NextResponse.json(data); }
  3. 클라이언트 컴포넌트에서 API Route로 요청:
    이제 클라이언트 컴포넌트에서는 Naver Maps API 서버로 직접 요청하는 대신, 위에서 만든 API Route로 요청을 보냅니다.
  4. // 클라이언트 컴포넌트 const getAddressFromCoords = async (lat, lng) => { const res = await fetch(`/api/naver-api?coords=${lng},${lat}&output=json`); const data = await res.json(); // ... 데이터 처리 };

이렇게 하면 브라우저는 같은 도메인(localhost:3000 -> localhost:3000/api/...)으로 요청을 보내고, Next.js 서버가 대신 Naver Maps API 서버에 요청을 보낸 뒤 결과를 받아와 클라이언트에 전달해 줍니다. 이는 보안적으로도 안전하고 CORS 문제도 깔끔하게 해결해 줍니다.


마무리

Naver Maps API는 직관적이고 강력한 기능을 제공하며, Next.js와 같은 최신 프레임워크와 함께 사용하면 더욱 매력적인 서비스들을 만들 수 있습니다. 이 글이 여러분의 지도 서비스 개발에 도움이 되었으면 좋겠습니다. 혹시 더 궁금한 점이 있으시면 댓글로 남겨주세요! 😊

728x90