| name | google-maps-js |
| description | [Applies to: **/*.{js,jsx}] This guide provides definitive best practices for integrating and managing the Google Maps JavaScript API in modern web applications, focusing on security, performance, and maintainability. |
| source | cursor_mdc |
google-maps-js Best Practices
This document outlines the definitive guidelines for working with the Google Maps JavaScript API. Adhering to these practices ensures secure, performant, and maintainable map-centric applications.
1. Code Organization and Structure
Encapsulate map logic to keep components clean and reusable. Avoid direct global google.maps object manipulation within React components.
✅ GOOD: Custom Hooks for Map Logic (React)
Abstract map initialization and interactions into custom hooks.
import { useEffect, useRef, useState } from 'react';
import { Loader } from '@googlemaps/js-api-loader';
const API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;
export function useGoogleMap(mapContainerRef, options) {
const [map, setMap] = useState(null);
const loaderRef = useRef(null);
useEffect(() => {
if (!mapContainerRef.current || !API_KEY) return;
if (!loaderRef.current) {
loaderRef.current = new Loader({
apiKey: API_KEY,
version: 'weekly',
});
}
const initMap = async () => {
try {
const { Map } = await loaderRef.current.importLibrary('maps');
const newMap = new Map(mapContainerRef.current, {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
mapId: 'YOUR_MAP_ID',
...options,
});
setMap(newMap);
} catch (error) {
console.error('Error loading Google Maps:', error);
}
};
initMap();
return () => {
setMap(null);
};
}, [mapContainerRef, options]);
return map;
}
import React, { useRef, useEffect } from 'react';
import { useGoogleMap } from '../hooks/useGoogleMap';
export function MyMapComponent() {
const mapRef = useRef(null);
const map = useGoogleMap(mapRef, { zoom: 10, center: { lat: 34.0522, lng: -118.2437 } });
useEffect(() => {
if (map) {
console.log('Map loaded:', map);
const marker = new google.maps.marker.AdvancedMarkerElement({
map,
position: map.getCenter(),
title: 'Hello World',
});
}
}, [map]);
return <div ref={mapRef} style={{ height: '500px', width: '100%' }} />;
}
2. Security
API key security is paramount. Never expose your API keys directly in client-side code.
❌ BAD: Hardcoding API Keys
const API_KEY = "YOUR_HARDCODED_API_KEY_HERE";
const loader = new Loader({ apiKey: API_KEY });
✅ GOOD: Secure API Key Management
Store API keys as environment variables and restrict them in the Google Cloud Console.
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=AIzaSy...
const API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;
3. Loading the API
Always use modern, performant methods for loading the Maps JavaScript API.
❌ BAD: Synchronous Script Tag
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"></script>
✅ GOOD: Dynamic Library Import with js-api-loader
This allows lazy loading of specific libraries as needed, reducing initial bundle size and improving page load performance.
import { Loader } from '@googlemaps/js-api-loader';
const loader = new Loader({
apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
version: 'weekly',
});
async function initializeMapAndPlaces() {
try {
const { Map } = await loader.importLibrary('maps');
const { AdvancedMarkerElement } = await loader.importLibrary('marker');
const { AutocompleteService } = await loader.importLibrary('places');
const map = new Map(document.getElementById('map'), { });
const marker = new AdvancedMarkerElement({ map, });
const autocompleteService = new AutocompleteService();
} catch (error) {
console.error(, error);
}
}
();
4. Event Handling
Manage event listeners carefully to prevent memory leaks and ensure proper cleanup.
❌ BAD: Unmanaged Event Listeners
useEffect(() => {
if (map) {
map.addListener('click', (e) => {
console.log('Map clicked at:', e.latLng.toString());
});
}
}, [map]);
✅ GOOD: Managed Event Listeners with Cleanup
Use google.maps.event.addListener and store the listener handle for later removal with google.maps.event.removeListener. In React, this is handled in useEffect's cleanup function.
useEffect(() => {
if (map) {
const clickListener = map.addListener('click', (e) => {
console.log('Map clicked at:', e.latLng.toString());
});
const dragEndListener = map.addListener('dragend', () => {
console.log('Map dragged to:', map.getCenter().toString());
});
return () => {
google.maps.event.removeListener(clickListener);
google.maps.event.removeListener(dragEndListener);
};
}
}, [map]);
5. Performance Considerations
Optimize map rendering and API requests to ensure a smooth user experience.
5.1 Marker Management
For applications with many markers, implement clustering or only render visible markers.
✅ GOOD: Marker Clustering
Use a helper library like @googlemaps/markerclusterer for efficient marker management.
import { MarkerClusterer } from '@googlemaps/markerclusterer';
async function addClusteredMarkers(map, locations) {
const { AdvancedMarkerElement } = await loader.importLibrary('marker');
const markers = locations.map(loc => new AdvancedMarkerElement({ position: loc }));
new MarkerClusterer({ map, markers });
}
5.2 Debouncing/Throttling Map Events
Avoid excessive re-renders or API calls on continuous events like mousemove or drag.
✅ GOOD: Debouncing idle Event
import { debounce } from 'lodash';
useEffect(() => {
if (map) {
const handleMapIdle = debounce(() => {
const center = map.getCenter();
const zoom = map.getZoom();
console.log(`Map idle. Center: ${center.toString()}, Zoom: ${zoom}`);
}, 500);
const idleListener = map.addListener('idle', handleMapIdle);
return () => {
google.maps.event.removeListener(idleListener);
handleMapIdle.cancel();
};
}
}, [map]);
6. Request/Response Patterns & Rate Limiting
All web service requests must be asynchronous, use HTTPS, and implement robust error handling with exponential backoff.
❌ BAD: Synchronous Requests & No Backoff
function getGeolocation(location) {
}
✅ GOOD: Asynchronous Requests with Exponential Backoff
For server-side or client-side web service calls (e.g., Geocoding, Places API web services), implement exponential backoff.
async function fetchWithExponentialBackoff(url, maxRetries = 5, initialDelay = 100) {
let delay = initialDelay;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
throw new Error(`API Error: ${response.status} ${response.statusText}`);
}
throw new Error(`Transient API Error: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.warn(`Attempt ${i + 1} failed: ${error.message}. Retrying in ${delay / }s...`);
(i === maxRetries - ) error;
( (resolve, delay));
delay *= ;
}
}
}
() {
encodedAddress = (address);
url = ;
{
data = (url);
(data. === ) {
data.[]..;
} {
();
}
} (error) {
.(, error);
}
}
6.1 URL Encoding
Always URL-encode parameters for web service requests, especially before signing if applicable.
const address = "5th&Main St.";
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${address}&key=...`;
const address = "5th&Main St.";
const encodedAddress = encodeURIComponent(address);
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodedAddress}&key=...`;
7. Common Pitfalls and Gotchas
7.1 Map ID Usage
Always use a Map ID when initializing a map. This enables cloud-based map styling and future features.