Use Cases (Updated: 6/2/2026)

Claude Code Google Maps Integration: Practical Next.js Guide

Use Claude Code to add Google Maps safely with Advanced Markers, geocoding, store locators, Mapbox, and checks.

Claude Code Google Maps Integration: Practical Next.js Guide

Design the Map Before Rendering It

Claude Code can add a map widget quickly. The production work is harder: API key restrictions, billing alerts, address search, mobile layout, privacy, server-side geocoding, and the difference between Google Maps and Mapbox.

The mistake Masa hit in an early store-locator prototype was not the marker itself. It was address search. Geocoding means converting an address into latitude and longitude. It is useful, but addresses are ambiguous, quota-backed, and tied to provider policies. If you mix browser keys and server keys, the app may work in development and still be insecure in production.

This guide treats Claude Code as an implementation partner, not as a copy-paste snippet generator. The examples use Next.js App Router, React, Google Maps JavaScript API, Advanced Markers, Geocoding API, and a small Mapbox GL JS alternative. Pair this with Claude Code security audits, performance optimization, and data visualization when the map becomes part of a real product.

Give Claude Code an Implementation Brief

Start with operating constraints, not with the library name. A map feature affects UX, cost, and legal obligations, so the request should be explicit.

Implement a store locator in Next.js App Router.

Requirements:
- Use the Google Maps JavaScript API
- Use AdvancedMarkerElement, not the legacy Marker class
- Read the browser key from NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
- Assume HTTP referrer restrictions and API restrictions are configured
- Call the Geocoding API from a server route with GOOGLE_MAPS_SERVER_KEY
- Keep the server key out of the browser bundle
- Sync address search, store list, marker clicks, and selected state
- Never read window or google during server-side rendering
- Include loading, error, empty, permission-denied, and mobile states
- Return a final checklist for API restrictions, billing alerts, and policy review

The implementation is easier to review when you split responsibilities.

flowchart LR
  User["User"]
  Page["Store locator page"]
  Map["Google Maps JS API"]
  Route["/api/geocode"]
  Google["Geocoding API"]
  Store["Store data"]
  Alerts["Billing alerts and logs"]

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

Use plain language in the brief. Geocoding is “turning an address into coordinates.” Reverse geocoding is “turning coordinates into a likely address.” A map ID is “the Google Cloud identifier used for styled maps and Advanced Markers.” That wording helps product owners review the plan without guessing what the technical terms mean.

Load Google Maps Safely in Next.js

Install the loader and type definitions first. The type package is not just polish; it helps catch outdated examples when Claude Code uses an API that no longer matches the current Maps JavaScript API.

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

Google’s current Advanced Marker guidance requires a map ID. The DEMO_MAP_ID works for quick local checks, but production should use a map ID created in Google Cloud Console. The browser API key is visible by design for Maps JavaScript API, so the practical security control is to restrict it. Follow the official Google Maps Platform security guidance: use HTTP referrer restrictions and restrict the key to only the APIs it needs.

// 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;
}

Now create the map component. The important details are SSR safety, marker cleanup, and avoiding user-provided HTML inside an InfoWindow.

// 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" />;
}

This gives you a reliable map surface. The next production boundary is address search.

Keep Geocoding on the Server

Use separate keys for browser maps and server geocoding. The browser key should be restricted by referrer. The server key should be restricted according to its execution environment, often by IP address for server-side web service calls. Check the official Geocoding request and response docs for status values and response structure.

// 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: "en",
    region: "us",
  });

  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,
  });
}

Do not add long-term caching just because quota is expensive. Geocoding storage and display rules depend on the Google Maps Platform terms and product policies. For a store locator, keep your official store coordinates in your own database, and use geocoding for user-entered searches or admin workflows where the policy is clear.

Build the Store Locator UI

The UI links address search, the list, the map center, and marker selection. This example is intentionally small enough to paste into a Next.js project and then adapt to your design system.

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

import { useMemo, useState } from "react";
import { GoogleBusinessMap, type MapPoint } from "@/components/GoogleBusinessMap";

type Store = {
  id: string;
  name: string;
  address: string;
  hours: string;
  phone: string;
  position: google.maps.LatLngLiteral;
};

type GeocodeResult = {
  formattedAddress: string;
  placeId: string;
  location: google.maps.LatLngLiteral;
};

const defaultCenter = { lat: 40.7128, lng: -74.006 };

export function StoreLocator({ stores }: { stores: Store[] }) {
  const [query, setQuery] = useState("");
  const [center, setCenter] = useState(stores[0]?.position ?? defaultCenter);
  const [selectedId, setSelectedId] = useState(stores[0]?.id ?? "");
  const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");

  const points = useMemo<MapPoint[]>(
    () =>
      stores.map((store) => ({
        id: store.id,
        title: store.name,
        lat: store.position.lat,
        lng: store.position.lng,
        category: "store",
      })),
    [stores],
  );

  async function searchAddress() {
    if (!query.trim()) return;

    setStatus("loading");

    const response = await fetch(`/api/geocode?address=${encodeURIComponent(query)}`);
    if (!response.ok) {
      setStatus("error");
      return;
    }

    const result = (await response.json()) as GeocodeResult;
    setCenter(result.location);
    setStatus("idle");
  }

  return (
    <section className="grid gap-4 lg:grid-cols-[320px_1fr]">
      <aside className="space-y-3">
        <div className="flex gap-2">
          <input
            value={query}
            onChange={(event) => setQuery(event.target.value)}
            onKeyDown={(event) => event.key === "Enter" && searchAddress()}
            placeholder="Search by address or city"
            className="min-w-0 flex-1 rounded-md border px-3 py-2"
          />
          <button onClick={searchAddress} className="rounded-md bg-blue-600 px-4 py-2 text-white">
            Search
          </button>
        </div>

        {status === "error" && <p className="text-sm text-red-600">No matching address found.</p>}

        <div className="max-h-[420px] space-y-2 overflow-auto">
          {stores.map((store) => (
            <button
              key={store.id}
              onClick={() => {
                setSelectedId(store.id);
                setCenter(store.position);
              }}
              className={`w-full rounded-md border p-3 text-left ${
                selectedId === store.id ? "border-blue-500 bg-blue-50" : "bg-white"
              }`}
            >
              <span className="block font-medium">{store.name}</span>
              <span className="block text-sm text-gray-600">{store.address}</span>
              <span className="block text-sm text-gray-500">{store.hours}</span>
            </button>
          ))}
        </div>
      </aside>

      <GoogleBusinessMap
        points={points}
        center={center}
        zoom={14}
        onSelect={(point) => setSelectedId(point.id)}
      />
    </section>
  );
}

When Mapbox Is the Better Fit

Google Maps is strong for store locators, address search, familiar map data, and business-oriented location workflows. Mapbox GL JS is often a better fit when you own a large geospatial dataset, need custom cartography, or want WebGL-driven layers and animations. The official Mapbox GL JS guides explain the map, style, source, and layer model.

Decision pointGoogle MapsMapbox GL JS
Store locatorStrong with Geocoding and PlacesGood when your data is primary
Visual controlCloud Styling is enough for most productsVery high style and layer control
Learning curveEasier for common web appsRequires understanding sources and layers
Operational riskKey restrictions and billing alertsToken restrictions and 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: [-74.006, 40.7128],
      zoom: 12,
    });

    map.addControl(new mapboxgl.NavigationControl(), "top-right");

    return () => map.remove();
  }, []);

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

The practical instruction for Claude Code is not “build both.” It is “use Google Maps for address search and store discovery; use Mapbox only for the custom data layer.” That keeps the architecture understandable.

Three Practical Use Cases

The first use case is a store locator for branches, clinics, classes, showrooms, or event venues. The core workflow is address search, current-location fallback, hours, phone number, booking CTA, and mobile usability. In Masa’s prototype, the first version gave the map too much vertical space on mobile. The better version showed the list first and moved the map when the user selected a location.

The second use case is real estate or lodging search. Price, availability, walking time, room size, filters, and map bounds all need to stay in sync. Showing every marker is usually a mistake. Ask Claude Code to render only results inside the current viewport, cluster dense areas, and keep list sorting consistent with the visible map.

The third use case is delivery, field sales, or maintenance operations. Here the map is an operations surface, not a decorative page element. Decide how often locations update, who can see them, how long they are stored, and what happens when the device denies geolocation permission. If you add route optimization, review the Routes or Directions API cost model before writing code.

A fourth useful pattern is editorial mapping: travel articles, local guides, event reports, and neighborhood pages. A map can improve exploration, but it should not replace useful prose. Keep directions, local context, accessibility notes, and decision criteria in the article body.

Pitfalls to Catch Before Production

The first pitfall is an unrestricted API key. Browser keys are visible, but they should still be restricted by referrer and API. Server keys should never be shipped to the browser.

The second pitfall is copying old Marker examples. Google now points developers to Advanced Markers, and the official Advanced Markers guide shows the AdvancedMarkerElement flow. If Claude Code emits old code, ask it to modernize the component.

The third pitfall is SSR. In Next.js, any top-level use of google.maps.Map can fail with google is not defined. Keep the component client-only and load Maps inside useEffect.

The fourth pitfall is ambiguous geocoding. “Central Station” or “Main Street” may return multiple plausible places. Use language, region, country, and structured inputs where possible, and do not use raw addresses as database IDs.

The fifth pitfall is performance. Rendering hundreds of markers and list cards at once makes both the map and the page feel slow. For larger datasets, use clustering, viewport queries, pagination, or server-side search.

The sixth pitfall is privacy. Current location requires user permission. The UI must work when permission is denied, and any stored location data needs a clear purpose and retention rule.

Review Checklist for Claude Code

After Claude Code generates the implementation, review it critically:

  • API keys are read from environment variables
  • browser and server keys are separated
  • Google Cloud API restrictions and application restrictions are documented
  • AdvancedMarkerElement is used
  • window and google are never referenced during SSR
  • zero results, ambiguous addresses, and API failures have UI states
  • user input is not injected into HTML strings
  • mobile layout keeps both list and map usable
  • billing alerts, quotas, and logs are part of the runbook

You can paste that checklist back into Claude Code and ask for a patch. That usually turns a demo map into something closer to production quality.

Summary and Verification Note

The safe way to integrate Google Maps with Claude Code is to split the job into map rendering, geocoding, key management, billing, privacy, and mobile UX. Google Maps is the default choice for store search and address workflows. Mapbox is strong when custom data layers and visual control are the main product value.

For this update, the code was checked for the parts that do not require a live provider key: Next.js responsibility boundaries, SSR-safe loading, code fence validity, and error branches. Before connecting a real key, configure Maps JavaScript API, Geocoding API, HTTP referrer restrictions, server-key restrictions, and budget alerts in Google Cloud Console. Start with three store records, verify search, marker click, mobile layout, and billing metrics, then expand to production data.

#Claude Code #Google Maps #Mapbox #maps #geolocation
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.