Pular para o conteúdo
Tutorial · Google Maps JavaScript API

Google Maps API em React e Next.js: TypeScript + @vis.gl

Tutorial completo de integração Google Maps JavaScript API com React Next.js. Aprenda geolocalização, mapas interativos, rotas dinâmicas e marcadores personalizados usando TypeScript e as melhores práticas de desenvolvimento web.

@vis.gl/react-google-maps · TypeScript · App Router · AdvancedMarker · Directions

Por que isso é importante

Google Maps React Next.js: use @vis.gl/react-google-maps com 'use client', APIProvider + Map/AdvancedMarker, key em NEXT_PUBLIC_ com restrição de referrer, e monitore billing/SKU — o crédito legado US$200 não é o modelo atual.

Recursos Poderosos

Tudo que você precisa para criar aplicações de mapeamento profissionais

  1. 01

    Geolocalização Avançada com Google Maps API

    Implemente geolocalização precisa usando Google Maps JavaScript API e navegador. Tutorial completo para obter coordenadas GPS do usuário em aplicações React Next.js.

  2. 02

    Rotas Dinâmicas e Directions API

    Crie rotas interativas com Google Maps Directions API. Aprenda a renderizar caminhos, calcular distâncias e implementar navegação turn-by-turn em React.

  3. 03

    Performance Otimizada Next.js

    Carregamento assíncrono de Google Maps com Next.js. Otimizações de performance, lazy loading e melhores práticas para aplicações web rápidas.

  4. 04

    TypeScript Google Maps Integration

    Tutorial TypeScript para Google Maps API. Tipagem completa, interfaces personalizadas e desenvolvimento type-safe com React e Next.js.

  5. 05

    Google Maps Services Completos

    Integração completa usando @googlemaps/google-maps-services-js e @googlemaps/js-api-loader. Places API, Geocoding e todas as funcionalidades.

  6. 06

    Customização e Marcadores Personalizados

    Crie marcadores personalizados, estilos de mapa únicos e componentes reutilizáveis. Tutorial completo de customização para Google Maps em React.

Configuração Rápida

Siga estes passos simples para começar a usar o Google Maps em seu projeto Next.js

  1. 1

    Instalação

    Instale as bibliotecas necessárias:

    terminal
    npm install @googlemaps/google-maps-services-js @googlemaps/js-api-loader
  2. 2

    API Key

    Configure sua chave de API do Google:

    .env.local
    NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=YOUR_API_KEY
  3. 3

    Configuração

    Importe e use em seu componente:

    component.tsx
    import { useLoadMap } from "./hooks"

Exemplos de Código

Códigos prontos para usar do tutorial completo do Google Maps API

Instalação e Configuração
# Instalação das dependências
npm install @googlemaps/js-api-loader

# Configuração da API Key
# Adicione no seu arquivo .env.local:
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=sua_api_key_aqui

# Tipos TypeScript (opcional)
npm install --save-dev @types/google.maps
Mapa Básico
import { Loader } from '@googlemaps/js-api-loader';
import { useEffect, useRef } from 'react';

function MapComponent() {
  const mapRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const initMap = async () => {
      const loader = new Loader({
        apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
        version: 'weekly',
        libraries: ['places']
      });

      const { Map } = await loader.importLibrary('maps');

      const map = new Map(mapRef.current!, {
        center: { lat: -23.5505, lng: -46.6333 }, // São Paulo
        zoom: 12,
        mapTypeId: 'roadmap'
      });
    };

    initMap();
  }, []);

  return (
    <div
      ref={mapRef}
      style={{ width: '100%', height: '400px' }}
    />
  );
}

export default MapComponent;
Marcadores e InfoWindows
import { Loader } from '@googlemaps/js-api-loader';
import { useEffect, useRef } from 'react';

function MapWithMarkers() {
  const mapRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const initMap = async () => {
      const loader = new Loader({
        apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
        version: 'weekly'
      });

      const { Map } = await loader.importLibrary('maps');
      const { Marker } = await loader.importLibrary('marker');

      const map = new Map(mapRef.current!, {
        center: { lat: -23.5505, lng: -46.6333 },
        zoom: 12
      });

      // Marcador simples
      const marker = new Marker({
        position: { lat: -23.5505, lng: -46.6333 },
        map: map,
        title: 'São Paulo'
      });

      // Marcador personalizado
      const customMarker = new Marker({
        position: { lat: -23.5489, lng: -46.6388 },
        map: map,
        title: 'Marco Zero',
        icon: {
          url: '/custom-marker.png',
          scaledSize: new google.maps.Size(40, 40)
        }
      });

      // InfoWindow
      const infoWindow = new google.maps.InfoWindow({
        content: '<h3>São Paulo</h3><p>Capital do estado de São Paulo</p>'
      });

      marker.addListener('click', () => {
        infoWindow.open(map, marker);
      });
    };

    initMap();
  }, []);

  return <div ref={mapRef} style={{ width: '100%', height: '400px' }} />;
}

export default MapWithMarkers;
Rotas e Direções
import { Loader } from '@googlemaps/js-api-loader';
import { useEffect, useRef, useState } from 'react';

function DirectionsMap() {
  const mapRef = useRef<HTMLDivElement>(null);
  const [directionsService, setDirectionsService] = useState<google.maps.DirectionsService | null>(null);
  const [directionsRenderer, setDirectionsRenderer] = useState<google.maps.DirectionsRenderer | null>(null);

  useEffect(() => {
    const initMap = async () => {
      const loader = new Loader({
        apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
        version: 'weekly',
        libraries: ['routes']
      });

      const { Map } = await loader.importLibrary('maps');
      const { DirectionsService, DirectionsRenderer } = await loader.importLibrary('routes');

      const map = new Map(mapRef.current!, {
        center: { lat: -23.5505, lng: -46.6333 },
        zoom: 12
      });

      const service = new DirectionsService();
      const renderer = new DirectionsRenderer({
        draggable: true,
        map: map
      });

      setDirectionsService(service);
      setDirectionsRenderer(renderer);

      // Calcular rota
      service.route({
        origin: { lat: -23.5505, lng: -46.6333 }, // São Paulo
        destination: { lat: -23.5489, lng: -46.6388 }, // Marco Zero
        travelMode: google.maps.TravelMode.DRIVING
      }, (result, status) => {
        if (status === 'OK' && result) {
          renderer.setDirections(result);
        }
      });
    };

    initMap();
  }, []);

  const calculateRoute = (origin: string, destination: string) => {
    if (!directionsService || !directionsRenderer) return;

    directionsService.route({
      origin: origin,
      destination: destination,
      travelMode: google.maps.TravelMode.DRIVING
    }, (result, status) => {
      if (status === 'OK' && result) {
        directionsRenderer.setDirections(result);
      }
    });
  };

  return (
    <div>
      <div style={{ marginBottom: '10px' }}>
        <button onClick={() => calculateRoute('São Paulo, SP', 'Rio de Janeiro, RJ')}>
          São Paulo → Rio de Janeiro
        </button>
        <button onClick={() => calculateRoute('São Paulo, SP', 'Campinas, SP')}>
          São Paulo → Campinas
        </button>
      </div>
      <div ref={mapRef} style={{ width: '100%', height: '400px' }} />
    </div>
  );
}

export default DirectionsMap;
Places API e Autocomplete
import { Loader } from '@googlemaps/js-api-loader';
import { useEffect, useRef, useState } from 'react';

function PlacesMap() {
  const mapRef = useRef<HTMLDivElement>(null);
  const searchRef = useRef<HTMLInputElement>(null);
  const [map, setMap] = useState<google.maps.Map | null>(null);

  useEffect(() => {
    const initMap = async () => {
      const loader = new Loader({
        apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
        version: 'weekly',
        libraries: ['places']
      });

      const { Map } = await loader.importLibrary('maps');
      const { PlacesService, Autocomplete } = await loader.importLibrary('places');

      const mapInstance = new Map(mapRef.current!, {
        center: { lat: -23.5505, lng: -46.6333 },
        zoom: 12
      });

      setMap(mapInstance);

      // Autocomplete
      if (searchRef.current) {
        const autocomplete = new Autocomplete(searchRef.current);
        autocomplete.bindTo('bounds', mapInstance);

        autocomplete.addListener('place_changed', () => {
          const place = autocomplete.getPlace();
          if (!place.geometry || !place.geometry.location) return;

          mapInstance.setCenter(place.geometry.location);
          mapInstance.setZoom(15);

          new google.maps.Marker({
            position: place.geometry.location,
            map: mapInstance,
            title: place.name
          });
        });
      }

      // Places Service
      const service = new PlacesService(mapInstance);

      // Buscar restaurantes próximos
      const request = {
        location: { lat: -23.5505, lng: -46.6333 },
        radius: 1000,
        type: 'restaurant'
      };

      service.nearbySearch(request, (results, status) => {
        if (status === google.maps.places.PlacesServiceStatus.OK && results) {
          results.slice(0, 5).forEach(place => {
            if (place.geometry?.location) {
              new google.maps.Marker({
                position: place.geometry.location,
                map: mapInstance,
                title: place.name,
                icon: {
                  url: 'https://maps.google.com/mapfiles/ms/icons/restaurant.png'
                }
              });
            }
          });
        }
      });
    };

    initMap();
  }, []);

  return (
    <div>
      <input
        ref={searchRef}
        type="text"
        placeholder="Buscar lugares..."
        style={{
          width: '300px',
          padding: '10px',
          marginBottom: '10px',
          border: '1px solid #ccc',
          borderRadius: '4px'
        }}
      />
      <div ref={mapRef} style={{ width: '100%', height: '400px' }} />
    </div>
  );
}

export default PlacesMap;

Tutorial Completo

Siga nosso guia passo a passo para dominar a integração do Google Maps com Next.js

~110 min total · 4 etapas

  1. 1

    Fácil · 15 min

    Configuração Inicial

    Instale as dependências e configure a API key do Google Maps

    • Instalação de pacotes
    • Configuração de variáveis de ambiente
    • Setup inicial do projeto
  2. 2

    Médio · 30 min

    Classes e Utilitários

    Implemente as classes RouteGoogle e MapGoogle para gerenciar mapas e rotas

    • Classe RouteGoogle
    • Classe MapGoogle
    • Funções utilitárias
    • Conversão de dados
  3. 3

    Médio · 25 min

    Hooks Personalizados

    Crie hooks React para carregar mapas e gerenciar rotas

    • Hook useLoadMap
    • Hook useCreateMapRoute
    • Geolocalização
    • Estado do formulário
  4. 4

    Avançado · 40 min

    Interface do Usuário

    Desenvolva componentes React para interação com mapas

    • Formulário de criação
    • Autocompletar locais
    • Visualização de rotas
    • Marcadores personalizados

Este é um material auxiliar do bootcamp CrazyStack Typescript do DevDoido. Ele servirá como uma espécie de documentação de alguns códigos vistos durante as aulas apenas como material complementar, garanta já sua vaga no bootcamp em crazystack.com.br.

Stack 2026: @vis.gl/react-google-maps no App Router

Caminho PT atual: pacote @vis.gl/react-google-maps, componente com 'use client', key em NEXT_PUBLIC_GOOGLE_MAPS_API_KEY só com restrição por HTTP referrer ativa. Tutoriais PT antigos ainda ensinam LoadScript legado — prefira APIProvider + Map da doc vis.gl (SSR não é suportado pela Maps JS API).

MyMap.tsx
'use client';

import { APIProvider, Map } from '@vis.gl/react-google-maps';

export default function MyMap() {
  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}>
      <Map
        mapId="SEU_MAP_ID"
        defaultZoom={12}
        defaultCenter={{ lat: -23.55, lng: -46.63 }}
        gestureHandling="greedy"
        style={{ width: '100%', height: 400 }}
      />
    </APIProvider>
  );
}

Fonte: guia SSR & frameworks do vis.gl — o directive use client exclui o mapa do SSR no App Router.

Checklist Cloud Console: billing, APIs e restrição de key

Antes do mapa na tela — sem isso o tutorial “funciona no localhost” e estoura na produção. Ponte natural para lead-gen / curso Maps Scrapper — sem inventar scrapes.

  1. Ative faturamento com alerta de orçamento (Maps Platform pós-2025 usa caps/SKU — não assuma crédito US$200 legado).
  2. Habilite Maps JavaScript API (e Places/Geocoding só se for usar).
  3. Crie a key com restrição por HTTP referrer do seu domínio.

Map ID, AdvancedMarker e InfoWindow

AdvancedMarker só funciona em mapa com cloud-based styling: crie um Map ID no Cloud Console e passe em mapId no componente Map (doc vis.gl AdvancedMarker).

  • InfoWindow: mantenha no máximo um aberto; feche o anterior ao clicar outro marker; conteúdo com texto/ARIA — nunca injete HTML cru de usuário.
  • Geolocation: peça permissão só sob gesto do usuário; trate deny/timeout; não assuma lat/lng sem fallback (centro padrão + mensagem).
  • Ponte lead-gen / curso Maps Scrapper: use Places/Geocoding só com APIs habilitadas e quota monitorada — sem inventar scrapes.

Código de rotas (Directions) — material do bootcamp

Este código TypeScript define uma classe RouteGoogle, que implementa RouteProtocol. Ela cria e gerencia rotas no Google Maps, com marcadores para início e destino, mais um marcador do veículo em movimento.

1. Importação de módulos e tipos:

route.google.ts
import type { DirectionsResponseData } from "@googlemaps/google-maps-services-js";
import { RouteProtocol } from "./route.protocol";
import { convertDirectionsResponseToDirectionsResult } from "./mapUtils";
  • Importa DirectionsResponseData de @googlemaps/google-maps-services-js. Serve pra lidar com os dados de resposta das direções do Google Maps.
  • Traz a interface RouteProtocol do módulo local route.protocol. Define o contrato que RouteGoogle precisa seguir.
  • Importa a função convertDirectionsResponseToDirectionsResult de mapUtils.

2. Declaração da classe RouteGoogle:

route.google.ts
export class RouteGoogle implements RouteProtocol {
  // Propriedades da classe
}

A classe RouteGoogle é exportada e segue RouteProtocol.

3. Construtor:

route.google.ts
constructor(options: {
  startMarkerOptions: google.maps.MarkerOptions;
  endMarkerOptions: google.maps.MarkerOptions;
  carMarkerOptions: google.maps.MarkerOptions;
}) {
  // Inicialização das propriedades da classe
}

O construtor recebe options com três campos: startMarkerOptions, endMarkerOptions e carMarkerOptions. Eles configuram os marcadores de início, fim e carro.

4. Inicialização de marcadores e DirectionsRenderer:

route.google.ts
this.startMarker = new google.maps.Marker(startMarkerOptions);
this.endMarker = new google.maps.Marker(endMarkerOptions);
this.carMarker = new google.maps.Marker(carMarkerOptions);
const { strokeColor } = this.startMarker.getIcon() as google.maps.Symbol;
this.directionsRenderer = new google.maps.DirectionsRenderer({
  suppressMarkers: true,
  polylineOptions: { strokeColor, strokeOpacity: 0.5, strokeWeight: 5 }
});
this.directionsRenderer.setMap(this.startMarker.getMap() as google.maps.Map);

Aqui criamos três marcadores (startMarker, endMarker e carMarker) usando as opções passadas. DirectionsRenderer é configurado pra desenhar a rota. O estilo da linha usa a cor do ícone do marcador inicial.

Código completo da classe RouteGoogle

route.google.ts
import type { DirectionsResponseData } from "@googlemaps/google-maps-services-js";
import { RouteProtocol } from "./route.protocol";
import { convertDirectionsResponseToDirectionsResult } from "./mapUtils";

export class RouteGoogle implements RouteProtocol {
  public startMarker: google.maps.Marker;
  public endMarker: google.maps.Marker;
  public carMarker: google.maps.Marker;
  public directionsRenderer: google.maps.DirectionsRenderer;

  constructor(options: {
    startMarkerOptions: google.maps.MarkerOptions;
    endMarkerOptions: google.maps.MarkerOptions;
    carMarkerOptions: google.maps.MarkerOptions;
  }) {
    const { startMarkerOptions, endMarkerOptions, carMarkerOptions } = options;
    this.startMarker = new google.maps.Marker(startMarkerOptions);
    this.endMarker = new google.maps.Marker(endMarkerOptions);
    this.carMarker = new google.maps.Marker(carMarkerOptions);
    const { strokeColor } = this.startMarker.getIcon() as google.maps.Symbol;
    this.directionsRenderer = new google.maps.DirectionsRenderer({
      suppressMarkers: true,
      polylineOptions: { strokeColor, strokeOpacity: 0.5, strokeWeight: 5 }
    });
    this.directionsRenderer.setMap(this.startMarker.getMap() as google.maps.Map);
  }

  delete(): void {
    this.startMarker.setMap(null);
    this.endMarker.setMap(null);
    this.carMarker.setMap(null);
    this.directionsRenderer.setMap(null);
  }

  async calculateRoute(
    directionsResponseData?: DirectionsResponseData & { request: any }
  ): Promise<void> {
    if (directionsResponseData) {
      const directionsResult = convertDirectionsResponseToDirectionsResult(
        directionsResponseData
      );
      this.directionsRenderer.setDirections(directionsResult);
    }
  }

  // Outros métodos da classe...
}

Pronto para Começar?

Transforme suas ideias em aplicações web poderosas com mapas interativos e funcionalidades avançadas de geolocalização

Tutorial Completo
Acesse o tutorial detalhado com exemplos práticos
Código Fonte
Baixe todos os arquivos e exemplos do projeto
CrazyStack
Participe do bootcamp completo de TypeScript

Material auxiliar do CrazyStack Typescript do DevDoido. Garanta já sua vaga no bootcamp e domine o desenvolvimento full-stack com TypeScript.

Fontes

Revisão em agosto de 2026. Créditos, caps e SKUs do Google Maps Platform mudam (ex. ajustes de 2025+) — o free tier legado de US$200 não deve ser assumido. Restrinja API keys e monitore billing no Console.

Google Maps JavaScript API · Maps — Billing March 2025 · @vis.gl/react-google-maps.

Perguntas frequentes

Como integrar Google Maps em React/Next.js com TypeScript?
Use @vis.gl/react-google-maps com APIProvider e um componente client ('use client'). A Maps JavaScript API não renderiza bem em SSR puro no App Router.
Funciona com App Router do Next.js?
Sim: isole o mapa num Client Component, exponha a key via NEXT_PUBLIC_ só se restringida por HTTP referrer, e evite importar o mapa em Server Components.
Quanto custa a Google Maps Platform?
Há crédito/quota e billing por SKU; a tabela mudou (caps/SKU desde 2025+). Não assuma o crédito legado US$200. Restrinja a key e monitore o Cloud Console.
Como adicionar markers e rotas?
Comece com Map + AdvancedMarker (Map ID quando exigido). Directions/Places são APIs extras — habilite no console e trate billing à parte.