Skip to main content
Technology & EngineeringMaps Geolocation Services268 lines

Google Maps Platform

"Google Maps Platform: Maps JavaScript API, Places, Geocoding, Directions, Street View, React (@vis.gl/react-google-maps)"

Quick Summary18 lines
Google Maps Platform provides the most widely recognized mapping experience, backed by comprehensive global data coverage and tight integration with the broader Google Cloud ecosystem. The Maps JavaScript API renders raster and vector maps with a familiar UX that users already understand. The platform's strength lies in its data APIs -- Places, Geocoding, Directions, Distance Matrix, and Street View -- which expose Google's continuously updated POI and routing graph. In React apps, use `@vis.gl/react-google-maps` (the official Google-sponsored library) rather than legacy community wrappers. Keep API key usage disciplined: restrict keys by HTTP referrer and API scope, and proxy sensitive calls through your backend to avoid client-side key exposure.

## Key Points

- **Use Map IDs**: Cloud-based map styling requires a Map ID. Create one in the Google Cloud Console and pass it as the `mapId` prop. This also unlocks Advanced Markers.
- **Restrict API keys**: In the Cloud Console, restrict keys by HTTP referrer for client-side keys and by IP for server-side keys. Enable only the specific APIs each key needs.
- **Load libraries on demand**: Use `useMapsLibrary("places")` to lazy-load the Places library only when needed, keeping the initial bundle small.
- **Proxy server-side calls**: Never call Geocoding, Directions, or Distance Matrix APIs from the browser with a server key. Route those calls through your API to protect the key and add caching.
- **Cache geocoding results**: Google's terms allow caching geocoding results for up to 30 days. Store frequently looked-up addresses in your database to reduce API costs.
- **Use session tokens for Autocomplete**: Pass a `sessionToken` to Autocomplete requests to bundle the autocomplete + place details into a single billing session.
- **Prefer AdvancedMarker over Marker**: The legacy `Marker` class is deprecated. `AdvancedMarker` supports custom HTML content and better accessibility.
- **Loading the entire Maps JavaScript API synchronously in the HTML head**: This blocks page rendering. Use the `APIProvider` component or load the API with `async defer`.
- **Creating new service instances on every render**: `DirectionsService`, `PlacesService`, and `Geocoder` should be instantiated once and reused. Store them in refs or outside the component.
- **Ignoring API billing tiers**: Places Autocomplete + Place Details charges per session. Street View charges per panorama load. Monitor usage in the Cloud Console billing dashboard.
- **Using client-side geocoding for batch jobs**: The JavaScript Geocoding service has aggressive rate limits. Use the server-side Geocoding API with exponential backoff for batch operations.
- **Forgetting to clean up listeners and renderers**: Google Maps event listeners and overlay objects (DirectionsRenderer, HeatmapLayer) must be removed in useEffect cleanup to prevent memory leaks.
skilldb get maps-geolocation-services-skills/Google Maps PlatformFull skill: 268 lines
Paste into your CLAUDE.md or agent config

Google Maps Platform

Core Philosophy

Google Maps Platform provides the most widely recognized mapping experience, backed by comprehensive global data coverage and tight integration with the broader Google Cloud ecosystem. The Maps JavaScript API renders raster and vector maps with a familiar UX that users already understand. The platform's strength lies in its data APIs -- Places, Geocoding, Directions, Distance Matrix, and Street View -- which expose Google's continuously updated POI and routing graph. In React apps, use @vis.gl/react-google-maps (the official Google-sponsored library) rather than legacy community wrappers. Keep API key usage disciplined: restrict keys by HTTP referrer and API scope, and proxy sensitive calls through your backend to avoid client-side key exposure.

Setup

Install dependencies and load the API:

// npm install @vis.gl/react-google-maps
import { APIProvider, Map, Marker, InfoWindow } from "@vis.gl/react-google-maps";

const GOOGLE_MAPS_API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY!;

function App() {
  return (
    <APIProvider apiKey={GOOGLE_MAPS_API_KEY}>
      <Map
        defaultCenter={{ lat: 40.7128, lng: -74.006 }}
        defaultZoom={12}
        style={{ width: "100%", height: "100vh" }}
        mapId="your-map-id" // required for advanced markers and cloud styling
      />
    </APIProvider>
  );
}

For server-side API calls (Geocoding, Directions), use the REST endpoints directly:

const GOOGLE_SERVER_KEY = process.env.GOOGLE_MAPS_SERVER_KEY!;

async function geocode(address: string): Promise<{ lat: number; lng: number } | null> {
  const url = new URL("https://maps.googleapis.com/maps/api/geocode/json");
  url.searchParams.set("address", address);
  url.searchParams.set("key", GOOGLE_SERVER_KEY);

  const res = await fetch(url);
  const data = await res.json();
  const location = data.results?.[0]?.geometry?.location;
  return location ?? null;
}

Key Techniques

Advanced Markers

import { AdvancedMarker, Pin } from "@vis.gl/react-google-maps";

interface Store {
  id: string;
  name: string;
  position: google.maps.LatLngLiteral;
  category: "restaurant" | "retail" | "office";
}

function StoreMarkers({ stores }: { stores: Store[] }) {
  const colorMap = { restaurant: "#e53e3e", retail: "#3182ce", office: "#38a169" };

  return (
    <>
      {stores.map((store) => (
        <AdvancedMarker key={store.id} position={store.position} title={store.name}>
          <Pin
            background={colorMap[store.category]}
            borderColor="#1a202c"
            glyphColor="#fff"
          />
        </AdvancedMarker>
      ))}
    </>
  );
}

Places Autocomplete

import { useMapsLibrary } from "@vis.gl/react-google-maps";
import { useEffect, useRef, useState } from "react";

function PlaceAutocomplete({ onSelect }: { onSelect: (place: google.maps.places.PlaceResult) => void }) {
  const inputRef = useRef<HTMLInputElement>(null);
  const places = useMapsLibrary("places");

  useEffect(() => {
    if (!places || !inputRef.current) return;

    const autocomplete = new places.Autocomplete(inputRef.current, {
      fields: ["geometry", "name", "formatted_address"],
      types: ["geocode", "establishment"],
    });

    autocomplete.addListener("place_changed", () => {
      const place = autocomplete.getPlace();
      if (place.geometry) onSelect(place);
    });
  }, [places, onSelect]);

  return <input ref={inputRef} type="text" placeholder="Search for a place..." />;
}

Directions Service

import { useMapsLibrary, useMap } from "@vis.gl/react-google-maps";
import { useEffect, useState } from "react";

interface RouteInfo {
  distance: string;
  duration: string;
  steps: google.maps.DirectionsStep[];
}

function useDirections(
  origin: google.maps.LatLngLiteral | null,
  destination: google.maps.LatLngLiteral | null
): RouteInfo | null {
  const map = useMap();
  const routes = useMapsLibrary("routes");
  const [routeInfo, setRouteInfo] = useState<RouteInfo | null>(null);

  useEffect(() => {
    if (!routes || !map || !origin || !destination) return;

    const service = new routes.DirectionsService();
    const renderer = new routes.DirectionsRenderer({ map });

    service.route(
      {
        origin,
        destination,
        travelMode: google.maps.TravelMode.DRIVING,
      },
      (result, status) => {
        if (status === "OK" && result) {
          renderer.setDirections(result);
          const leg = result.routes[0].legs[0];
          setRouteInfo({
            distance: leg.distance?.text ?? "",
            duration: leg.duration?.text ?? "",
            steps: leg.steps,
          });
        }
      }
    );

    return () => renderer.setMap(null);
  }, [routes, map, origin, destination]);

  return routeInfo;
}

Street View

import { useRef, useEffect } from "react";
import { useMapsLibrary } from "@vis.gl/react-google-maps";

function StreetViewPane({ position }: { position: google.maps.LatLngLiteral }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const streetView = useMapsLibrary("streetView");

  useEffect(() => {
    if (!streetView || !containerRef.current) return;

    new google.maps.StreetViewPanorama(containerRef.current, {
      position,
      pov: { heading: 165, pitch: 0 },
      zoom: 1,
    });
  }, [streetView, position]);

  return <div ref={containerRef} style={{ width: "100%", height: 400 }} />;
}

Drawing on the Map

function useDrawingManager(
  onPolygonComplete: (polygon: google.maps.Polygon) => void
) {
  const map = useMap();
  const drawing = useMapsLibrary("drawing");

  useEffect(() => {
    if (!drawing || !map) return;

    const manager = new drawing.DrawingManager({
      drawingMode: google.maps.drawing.OverlayType.POLYGON,
      drawingControl: true,
      drawingControlOptions: {
        position: google.maps.ControlPosition.TOP_CENTER,
        drawingModes: [google.maps.drawing.OverlayType.POLYGON],
      },
      polygonOptions: {
        fillColor: "#3182ce",
        fillOpacity: 0.3,
        strokeWeight: 2,
        editable: true,
      },
    });

    manager.setMap(map);
    google.maps.event.addListener(manager, "polygoncomplete", onPolygonComplete);

    return () => {
      manager.setMap(null);
      google.maps.event.clearInstanceListeners(manager);
    };
  }, [drawing, map, onPolygonComplete]);
}

Heatmap Layer

function useHeatmap(points: google.maps.LatLngLiteral[]) {
  const map = useMap();
  const visualization = useMapsLibrary("visualization");

  useEffect(() => {
    if (!visualization || !map || points.length === 0) return;

    const heatmap = new google.maps.visualization.HeatmapLayer({
      data: points.map((p) => new google.maps.LatLng(p.lat, p.lng)),
      radius: 30,
      opacity: 0.7,
    });

    heatmap.setMap(map);
    return () => heatmap.setMap(null);
  }, [visualization, map, points]);
}

Best Practices

  • Use Map IDs: Cloud-based map styling requires a Map ID. Create one in the Google Cloud Console and pass it as the mapId prop. This also unlocks Advanced Markers.
  • Restrict API keys: In the Cloud Console, restrict keys by HTTP referrer for client-side keys and by IP for server-side keys. Enable only the specific APIs each key needs.
  • Load libraries on demand: Use useMapsLibrary("places") to lazy-load the Places library only when needed, keeping the initial bundle small.
  • Proxy server-side calls: Never call Geocoding, Directions, or Distance Matrix APIs from the browser with a server key. Route those calls through your API to protect the key and add caching.
  • Cache geocoding results: Google's terms allow caching geocoding results for up to 30 days. Store frequently looked-up addresses in your database to reduce API costs.
  • Use session tokens for Autocomplete: Pass a sessionToken to Autocomplete requests to bundle the autocomplete + place details into a single billing session.
  • Prefer AdvancedMarker over Marker: The legacy Marker class is deprecated. AdvancedMarker supports custom HTML content and better accessibility.

Anti-Patterns

  • Loading the entire Maps JavaScript API synchronously in the HTML head: This blocks page rendering. Use the APIProvider component or load the API with async defer.
  • Creating new service instances on every render: DirectionsService, PlacesService, and Geocoder should be instantiated once and reused. Store them in refs or outside the component.
  • Ignoring API billing tiers: Places Autocomplete + Place Details charges per session. Street View charges per panorama load. Monitor usage in the Cloud Console billing dashboard.
  • Using client-side geocoding for batch jobs: The JavaScript Geocoding service has aggressive rate limits. Use the server-side Geocoding API with exponential backoff for batch operations.
  • Forgetting to clean up listeners and renderers: Google Maps event listeners and overlay objects (DirectionsRenderer, HeatmapLayer) must be removed in useEffect cleanup to prevent memory leaks.
  • Hardcoding coordinates instead of using the Geocoding API: Coordinates drift as addresses change. Geocode at display time or re-geocode periodically for mission-critical location data.

Install this skill directly: skilldb add maps-geolocation-services-skills

Get CLI access →