Use Cases (업데이트: 2026. 6. 2.)

Claude Code로 Google Maps를 Web 앱에 통합하는 Next.js 실전 가이드

Claude Code로 Google Maps를 안전하게 연결하고 Advanced Markers, 지오코딩, 매장 검색, Mapbox 선택 기준을 구현합니다.

Claude Code로 Google Maps를 Web 앱에 통합하는 Next.js 실전 가이드

지도를 그리기 전에 운영 조건을 먼저 정한다

Claude Code에게 Google Maps를 붙여 달라고 하면 지도는 금방 보입니다. 하지만 실제 서비스에서 중요한 것은 표시 자체가 아니라 API 키 제한, 과금 알림, 주소 검색, 모바일 배치, 위치 권한 거부, 서버와 브라우저의 책임 분리입니다.

Masa가 매장 검색 프로토타입에서 처음 막힌 부분도 마커가 아니라 주소 검색이었습니다. 지오코딩은 주소를 위도와 경도로 바꾸는 작업입니다. 간단한 API 호출처럼 보이지만 같은 지명이 여러 장소를 뜻할 수 있고, 호출량은 비용으로 이어지며, 결과 저장과 표시 방식도 Google Maps Platform 정책을 확인해야 합니다.

이 글은 Claude Code를 단순 코드 생성기가 아니라 지도 기능을 함께 설계하는 파트너로 쓰는 방법을 정리합니다. 예시는 Next.js App Router, React, Google Maps JavaScript API, Advanced Marker, Geocoding API, 그리고 Mapbox GL JS 대안을 다룹니다. API 키와 권한은 Claude Code 보안 감사, 속도는 Claude Code 성능 최적화, 지리 데이터 표현은 Claude Code 데이터 시각화와 같이 보면 좋습니다.

Claude Code에 줄 구현 브리프

라이브러리 이름만 주면 Claude Code는 데모 코드를 만들 가능성이 높습니다. 실제 앱에 필요한 제약을 먼저 적어야 합니다.

Next.js App Router에서 매장 검색 페이지를 구현해 주세요.

요구사항:
- Google Maps JavaScript API 사용
- 기존 Marker가 아니라 AdvancedMarkerElement 사용
- 브라우저 키는 NEXT_PUBLIC_GOOGLE_MAPS_API_KEY에서 읽기
- HTTP referrer 제한과 API 제한이 설정된 전제로 작성
- Geocoding API는 서버 라우트에서만 호출하고 GOOGLE_MAPS_SERVER_KEY 사용
- 서버 키는 브라우저 번들에 포함하지 않기
- 주소 검색, 매장 목록, 마커 클릭, 선택 상태를 동기화
- SSR 중 window 또는 google을 참조하지 않기
- loading, error, empty, 위치 권한 거부, 모바일 상태 처리
- 마지막에 API 제한, 예산 알림, 정책 확인 체크리스트 출력

구성은 다음처럼 나누면 검토하기 쉽습니다.

flowchart LR
  User["사용자"]
  Page["매장 검색 페이지"]
  Map["Google Maps JS API"]
  Route["/api/geocode"]
  Google["Geocoding API"]
  Store["매장 데이터"]
  Alerts["과금 알림과 로그"]

  User --> Page
  Page --> Map
  Page --> Store
  Page --> Route
  Route --> Google
  Route --> Alerts

팀 안에서 설명할 때는 용어도 풀어 쓰는 편이 좋습니다. Geocoding은 주소를 좌표로 바꾸는 일, reverse geocoding은 좌표에서 주소를 추정하는 일, map ID는 Google Cloud에서 지도 스타일과 Advanced Marker에 쓰는 식별자입니다.

Next.js에서 Google Maps 안전하게 로드하기

먼저 로더와 타입 정의를 설치합니다. 타입이 있으면 Claude Code가 오래된 예시를 가져왔을 때 더 빨리 잡을 수 있습니다.

npm i @googlemaps/js-api-loader
npm i -D @types/google.maps

Google의 Advanced Marker 문서는 map ID 사용을 전제로 합니다. 개발 중에는DEMO_MAP_ID로 최소 확인이 가능하지만, 운영 환경에서는 Google Cloud Console에서 만든 map ID를 써야 합니다. Maps JavaScript API용 브라우저 키는 노출될 수밖에 있으므로 Google Maps Platform 보안 가이드에 따라 HTTP referrer 제한과 API 제한을 설정해야 합니다.

// src/lib/google-maps-loader.ts
import { Loader } from "@googlemaps/js-api-loader";

let googleMapsPromise: Promise<typeof google> | null = null;

export function loadGoogleMaps() {
  const apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;

  if (!apiKey) {
    throw new Error("NEXT_PUBLIC_GOOGLE_MAPS_API_KEY is missing");
  }

  if (!googleMapsPromise) {
    const loader = new Loader({
      apiKey,
      version: "weekly",
      libraries: ["marker", "places"],
    });

    googleMapsPromise = loader.load();
  }

  return googleMapsPromise;
}

지도 컴포넌트는 클라이언트 컴포넌트로 만들고, API 로드는useEffect안에서 처리합니다. 마커를 다시 그릴 때 이전 마커를 제거하지 않으면 이벤트 리스너가 남고, 페이지 전환 후에도 메모리가 새어 나갈 수 있습니다.

// src/components/GoogleBusinessMap.tsx
"use client";

import { useEffect, useRef } from "react";
import { loadGoogleMaps } from "@/lib/google-maps-loader";

export type MapPoint = {
  id: string;
  title: string;
  lat: number;
  lng: number;
  category?: "store" | "warehouse" | "property";
};

type Props = {
  points: MapPoint[];
  center: google.maps.LatLngLiteral;
  zoom?: number;
  onSelect?: (point: MapPoint) => void;
};

export function GoogleBusinessMap({ points, center, zoom = 13, onSelect }: Props) {
  const mapRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let cancelled = false;
    let markers: google.maps.marker.AdvancedMarkerElement[] = [];

    async function renderMap() {
      await loadGoogleMaps();
      if (!mapRef.current || cancelled) return;

      const { Map } = (await google.maps.importLibrary("maps")) as google.maps.MapsLibrary;
      const { AdvancedMarkerElement, PinElement } = (await google.maps.importLibrary(
        "marker",
      )) as google.maps.MarkerLibrary;

      const map = new Map(mapRef.current, {
        center,
        zoom,
        mapId: process.env.NEXT_PUBLIC_GOOGLE_MAPS_MAP_ID ?? "DEMO_MAP_ID",
        fullscreenControl: false,
        gestureHandling: "cooperative",
      });

      markers = points.map((point, index) => {
        const pin = new PinElement({
          glyph: String(index + 1),
          background: point.category === "warehouse" ? "#0f766e" : "#2563eb",
          borderColor: "#ffffff",
          glyphColor: "#ffffff",
        });

        const marker = new AdvancedMarkerElement({
          map,
          position: { lat: point.lat, lng: point.lng },
          title: point.title,
          content: pin.element,
        });

        marker.addListener("click", () => onSelect?.(point));
        return marker;
      });
    }

    renderMap().catch((error) => console.error("Failed to render Google Map", error));

    return () => {
      cancelled = true;
      markers.forEach((marker) => {
        marker.map = null;
      });
    };
  }, [center.lat, center.lng, points, zoom, onSelect]);

  return <div ref={mapRef} className="h-[420px] w-full rounded-lg border" />;
}

사용자 입력을 문자열 HTML로 만들어 InfoWindow에 넣는 방식은 피합니다. 매장명과 주소가 CMS에서 들어오면 XSS 검토 대상이 되므로 텍스트로 렌더링하는 구조가 안전합니다.

지오코딩은 서버 라우트로 분리하기

브라우저용 Maps JavaScript API 키와 서버용 Geocoding API 키는 분리합니다. 브라우저 키는 HTTP referrer 제한, 서버 키는 실행 환경에 맞는 제한을 적용합니다. 상태 코드와 응답 형식은 Geocoding API 요청과 응답 문서를 기준으로 확인합니다.

// src/app/api/geocode/route.ts
import { NextResponse } from "next/server";

type GeocodeResponse = {
  status: string;
  error_message?: string;
  results: Array<{
    formatted_address: string;
    place_id: string;
    geometry: { location: { lat: number; lng: number } };
  }>;
};

const endpoint = "https://maps.googleapis.com/maps/api/geocode/json";

export async function GET(request: Request) {
  const key = process.env.GOOGLE_MAPS_SERVER_KEY;
  const { searchParams } = new URL(request.url);
  const address = searchParams.get("address")?.trim();

  if (!key) return NextResponse.json({ error: "Server key is missing" }, { status: 500 });
  if (!address || address.length > 180) {
    return NextResponse.json({ error: "Address is required" }, { status: 400 });
  }

  const params = new URLSearchParams({ address, key, language: "ko", region: "kr" });
  const response = await fetch(`${endpoint}?${params}`, { cache: "no-store" });
  const data = (await response.json()) as GeocodeResponse;
  const first = data.results[0];

  if (!response.ok || data.status !== "OK" || !first) {
    return NextResponse.json(
      { error: data.error_message ?? data.status },
      { status: data.status === "ZERO_RESULTS" ? 404 : 502 },
    );
  }

  return NextResponse.json({
    formattedAddress: first.formatted_address,
    placeId: first.place_id,
    location: first.geometry.location,
  });
}

비용을 줄이기 위해 무조건 캐시를 길게 두는 것은 위험합니다. 지오코딩 결과를 저장해도 되는지, 어떤 지도에 표시할 수 있는지, 얼마나 보관할 수 있는지는 Google Maps Platform 약관과 제품 정책을 확인해야 합니다. 공식 매장 좌표는 자체 데이터로 관리하고, 사용자가 입력한 검색은 필요한 범위에서만 처리하는 편이 안전합니다.

매장 검색, 부동산, 운영 지도에서의 활용

첫 번째 활용은 매장 검색입니다. 지점, 병원, 학원, 전시장, 행사장은 주소 검색, 현재 위치, 영업시간, 전화번호, 예약 버튼이 핵심입니다. Masa의 초기 버전은 모바일에서 지도가 너무 커서 목록을 보기 어려웠습니다. 실제 사용자는 먼저 후보를 스캔하므로 모바일에서는 목록을 먼저 두고, 선택 시 지도 중심을 이동하는 패턴이 더 나았습니다.

두 번째는 부동산이나 숙박 검색입니다. 가격, 면적, 공실 여부, 역까지의 거리, 필터, 지도 범위가 서로 맞아야 합니다. 모든 마커를 한 번에 그리는 대신 현재 viewport에 보이는 결과만 가져오고, 밀집 지역은 클러스터링하며, 목록 정렬도 지도 범위와 일치시켜야 합니다.

세 번째는 배송, 방문 영업, 유지보수 관리 화면입니다. 이 경우 지도는 장식이 아니라 운영 도구입니다. 위치 업데이트 주기, 열람 권한, 보관 기간, 위치 권한 거부 시 동작을 먼저 정해야 합니다. 경로 최적화까지 넣는다면 Routes API나 Directions API 비용 모델도 코드 작성 전에 확인합니다.

네 번째는 여행 글, 지역 가이드, 이벤트 리포트 같은 콘텐츠 지도입니다. 지도는 탐색성을 높이지만 본문을 대체할 수 없습니다. 이동 방법, 현장 주의점, 선택 기준을 텍스트로 남겨야 SEO와 독자 경험이 유지됩니다.

Mapbox를 선택할 때

Google Maps는 매장 검색, 주소 검색, Places 데이터, 익숙한 지도 경험에 강합니다. Mapbox GL JS는 자체 지리 데이터가 많거나 브랜드에 맞춘 지도 스타일, WebGL 레이어, 애니메이션이 중요할 때 강합니다. 기본 개념은 Mapbox GL JS 가이드에서 확인할 수 있습니다.

기준Google MapsMapbox GL JS
매장 검색Geocoding, Places와 결합하기 좋음자체 데이터 중심이면 적합
시각 제어Cloud Styling으로 충분한 경우가 많음스타일과 레이어 제어가 강함
학습 비용일반 웹앱에 도입하기 쉬움source, layer, style 이해 필요
운영 리스크키 제한과 과금 알림 필수토큰 제한과 attribution 필수
// src/components/MapboxPreview.tsx
"use client";

import { useEffect, useRef } from "react";
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";

export function MapboxPreview() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const token = process.env.NEXT_PUBLIC_MAPBOX_TOKEN;
    if (!containerRef.current || !token) return;

    mapboxgl.accessToken = token;

    const map = new mapboxgl.Map({
      container: containerRef.current,
      style: "mapbox://styles/mapbox/streets-v12",
      center: [126.978, 37.5665],
      zoom: 12,
    });

    map.addControl(new mapboxgl.NavigationControl(), "top-right");
    return () => map.remove();
  }, []);

  return <div ref={containerRef} className="h-[420px] w-full rounded-lg border" />;
}

Claude Code에는 “둘 다 만들어 줘”보다 “주소 검색은Google Maps, 자체 데이터 레이어는Mapbox”처럼 역할을 나눠 지시하는 편이 좋습니다.

운영 전에 확인할 함정

가장 위험한 것은 제한 없는 API 키 공개입니다. 브라우저 키는 보이지만 referrer와 API 제한을 걸어야 합니다. 서버 키는 절대 브라우저 번들에 들어가면 안 됩니다.

두 번째는 오래된Marker 예시입니다. Google은 Advanced Markers 가이드에서AdvancedMarkerElement사용을 안내합니다. Claude Code가 오래된 코드를 만들면 최신 방식으로 바꾸라고 요청합니다.

세 번째는 SSR입니다. Next.js에서 최상위 스코프에google.maps.Map을 쓰면google is not defined가 납니다. 컴포넌트를"use client"로 두고useEffect에서 로드해야 합니다.

네 번째는 주소의 모호함입니다. 짧은 지명이나 역 이름은 여러 후보를 만들 수 있습니다. 언어, 지역, 국가, 구조화 입력, 후보 선택 UI를 사용하고 원본 주소 문자열을 DB 키로 쓰지 않습니다.

다섯 번째는 성능입니다. 수백 개 마커와 목록을 동시에 그리면 페이지가 느려집니다. 데이터가 많으면 클러스터링, viewport 조회, 페이지네이션, 서버 검색을 고려합니다.

여섯 번째는 개인정보입니다. 현재 위치는 사용자 허가가 필요합니다. 거부해도 페이지가 작동해야 하고, 저장한다면 목적과 보관 기간을 설명해야 합니다.

정리와 검증 메모

Claude Code로 Google Maps를 통합할 때 핵심은 지도 표시, 주소 검색, 키 관리, 과금, 개인정보, 모바일 UX를 분리해 다루는 것입니다. Google Maps는 매장과 주소 워크플로에 강하고, Mapbox는 자체 데이터 레이어와 시각 표현에 강합니다.

이번 업데이트에서는 실제 API 키 없이 확인 가능한 범위, 즉 Next.js 책임 분리, SSR 안전 로딩, 오류 분기, 코드 블록 형식을 점검했습니다. 실제 키를 연결하기 전에는 Google Cloud Console에서 Maps JavaScript API, Geocoding API, HTTP referrer 제한, 서버 키 제한, 예산 알림을 설정하세요. 먼저 3개의 매장 데이터로 검색, 마커 클릭, 모바일 레이아웃, 과금 지표를 확인한 뒤 운영 데이터로 넓히는 것이 안전합니다.

#Claude Code #Google Maps #Mapbox #지도 앱 #위치 정보
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.