Integrar Google Maps con Claude Code: guía práctica para Next.js
Integra Google Maps con Claude Code: Advanced Markers, geocoding, tiendas, Mapbox y controles de producción.
Diseña la función de mapa antes de mostrarlo
Claude Code puede insertar un mapa rápido. Lo difícil en producción es decidir cómo se restringen las claves de API, dónde vive la búsqueda de direcciones, cómo se controla el gasto, qué pasa en móvil y cómo se responde cuando el usuario rechaza compartir su ubicación.
El tropiezo de Masa en un prototipo de buscador de tiendas no fue el marcador, sino la dirección. Geocoding significa convertir una dirección en latitud y longitud. Parece una llamada sencilla, pero una dirección puede ser ambigua, cada llamada puede afectar el presupuesto y las reglas de almacenamiento o visualización dependen de la plataforma.
Esta guía usa Claude Code como compañero de implementación, no como generador de snippets. El ejemplo se apoya en Next.js App Router, React, Google Maps JavaScript API, Advanced Markers, Geocoding API y una alternativa breve con Mapbox GL JS. Para seguridad de claves, revisa también auditoría de seguridad con Claude Code; para velocidad, optimización de rendimiento; y para capas de datos, visualización de datos.
El briefing que debes dar a Claude Code
No empieces con “añade Google Maps”. Empieza con condiciones de operación. Así evitas que Claude Code entregue un demo que funciona localmente pero queda débil para producción.
Implementa un buscador de tiendas con Next.js App Router.
Requisitos:
- Usar Google Maps JavaScript API
- Usar AdvancedMarkerElement, no la clase Marker antigua
- Leer la clave del navegador desde NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
- Asumir restricciones HTTP referrer y restricciones de API
- Llamar Geocoding API desde una ruta de servidor con GOOGLE_MAPS_SERVER_KEY
- No exponer la clave de servidor al bundle del navegador
- Sincronizar búsqueda, lista de tiendas, clics en marcadores y estado seleccionado
- No leer window ni google durante SSR
- Cubrir loading, error, vacío, permiso de ubicación denegado y móvil
- Entregar checklist final de restricciones, alertas de presupuesto y revisión de políticas
La arquitectura queda más clara si separas responsabilidades.
flowchart LR
User["Usuario"]
Page["Página de tiendas"]
Map["Google Maps JS API"]
Route["/api/geocode"]
Google["Geocoding API"]
Store["Datos de tiendas"]
Alerts["Alertas y logs"]
User --> Page
Page --> Map
Page --> Store
Page --> Route
Route --> Google
Route --> Alerts
También conviene traducir los términos. Geocoding es convertir una dirección en coordenadas. Reverse geocoding es inferir una dirección desde coordenadas. Map ID es el identificador de Google Cloud que se usa para estilos de mapa y Advanced Markers. Esa claridad ayuda a que producto y soporte revisen la función.
Cargar Google Maps de forma segura en Next.js
Instala el loader y los tipos. Los tipos ayudan a detectar ejemplos obsoletos cuando Claude Code usa propiedades que no encajan con la API actual.
npm i @googlemaps/js-api-loader
npm i -D @types/google.maps
La guía actual de Advanced Markers requiere un map ID. DEMO_MAP_ID sirve para pruebas locales, pero producción debe usar un map ID creado en Google Cloud Console. La clave del navegador se verá porque Maps JavaScript API funciona así; el control real es restringirla. Sigue la guía de seguridad de Google Maps Platform: HTTP referrers y restricciones de 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;
}
El componente de mapa debe ser cliente y cargar la API dentro deuseEffect. También debe limpiar marcadores al desmontarse para evitar listeners duplicados.
// 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" />;
}
Evita construir HTML con strings de usuario para meterlo en unInfoWindow. Los nombres de tiendas, direcciones y notas suelen venir de un CMS o panel interno; trátalos como texto.
Mover geocoding al servidor
Separa la clave del navegador y la clave de servidor. La primera usa referrers; la segunda debe tener restricciones adecuadas al entorno de ejecución. Consulta la documentación oficial de solicitudes y respuestas de Geocoding para estados y estructura.
// 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: "es", region: "es" });
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,
});
}
No conviertas el cacheo en reflejo automático. Guardar resultados de geocoding depende de términos y políticas del producto. Para un localizador, guarda como datos propios las coordenadas oficiales de tus tiendas y usa geocoding para búsquedas del usuario o flujos administrativos bien definidos.
Casos de uso reales
El primer caso es el buscador de tiendas: sucursales, clínicas, aulas, showrooms o sedes de eventos. Necesitas búsqueda por dirección, ubicación actual como ayuda, horarios, teléfono y CTA de reserva. En el prototipo de Masa, el primer diseño daba demasiado espacio al mapa en móvil y escondía la lista. Funcionó mejor mostrar la lista primero y mover el mapa al seleccionar una tienda.
El segundo caso es inmobiliaria u hospedaje. Precio, disponibilidad, superficie, distancia caminando, filtros y límites del mapa deben sincronizarse. Mostrar todos los marcadores es una mala idea cuando hay muchos resultados. Pide a Claude Code consultar solo el viewport actual, agrupar zonas densas y mantener la lista alineada con lo visible.
El tercer caso es operación de campo: reparto, ventas externas o mantenimiento. Aquí el mapa es una herramienta de trabajo. Decide frecuencia de actualización, permisos de lectura, retención de ubicación y comportamiento cuando el dispositivo niega geolocalización. Si agregas rutas, revisa primero costos de Routes API o Directions API.
Un cuarto caso es contenido editorial: guías de viaje, barrios, eventos y recomendaciones locales. El mapa aumenta exploración, pero no reemplaza el texto útil. Mantén indicaciones, contexto local, accesibilidad y criterios de decisión en el cuerpo del artículo.
Cuándo elegir Mapbox
Google Maps encaja muy bien con búsqueda de tiendas, direcciones, Places y una experiencia conocida. Mapbox GL JS es fuerte cuando los datos geográficos son tuyos, necesitas cartografía de marca o quieres capas WebGL. La base está en las guías de Mapbox GL JS.
| Criterio | Google Maps | Mapbox GL JS |
|---|---|---|
| Tiendas y direcciones | Muy práctico con Geocoding y Places | Bueno si el dato principal es propio |
| Control visual | Cloud Styling suele bastar | Control alto de estilos y capas |
| Curva de aprendizaje | Más simple para apps comunes | Requiere entender sources, layers y styles |
| Riesgo operativo | Restricciones y alertas de coste | Tokens y atribución obligatoria |
// 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: [-3.7038, 40.4168],
zoom: 12,
});
map.addControl(new mapboxgl.NavigationControl(), "top-right");
return () => map.remove();
}, []);
return <div ref={containerRef} className="h-[420px] w-full rounded-lg border" />;
}
La instrucción práctica no es “hazlo con ambos”. Es “usa Google Maps para búsqueda de direcciones y Mapbox para la capa de datos propia”. Esa división evita una arquitectura confusa.
Errores que debes detectar
El primero es publicar una clave sin restricciones. La clave del navegador se ve, pero debe limitarse por referrer y API. La clave de servidor nunca debe llegar al bundle del navegador.
El segundo es copiar ejemplos antiguos deMarker. Google orienta a los desarrolladores hacia Advanced Markers; la guía oficial de Advanced Markers muestra el flujo conAdvancedMarkerElement.
El tercero es SSR. En Next.js, leergoogle.maps.Map en el nivel superior producegoogle is not defined. Mantén el mapa como componente cliente y carga la API dentro deuseEffect.
El cuarto es la ambigüedad de direcciones. Un barrio, estación o calle puede devolver varios candidatos. Usa idioma, región, país, entradas estructuradas o selección de candidatos. No uses la dirección cruda como identificador principal de base de datos.
El quinto es rendimiento. Cientos de marcadores y tarjetas hacen lenta la página. Cuando superes decenas altas de puntos, usa clustering, consultas por viewport, paginación o búsqueda de servidor.
El sexto es privacidad. La ubicación actual requiere permiso. La UI debe funcionar si el usuario lo niega y, si guardas ubicación, debes explicar propósito y periodo de retención.
Resumen y verificación práctica
La forma segura de integrar Google Maps con Claude Code es separar renderizado de mapa, geocoding, gestión de claves, presupuesto, privacidad y UX móvil. Google Maps es la opción fuerte para direcciones y tiendas; Mapbox destaca cuando el valor está en capas de datos propias y control visual.
En esta actualización se revisó lo comprobable sin claves reales: división de responsabilidades en Next.js, carga segura frente a SSR, ramas de error y formato de bloques de código. Antes de usar claves reales, configura Maps JavaScript API, Geocoding API, restricciones HTTP referrer, restricciones para la clave de servidor y alertas de presupuesto en Google Cloud Console. Prueba primero con tres tiendas, valida búsqueda, clic en marcador, móvil y métricas de facturación, y después pasa a datos de producción.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.