O campo de endereço do checkout é onde as conversões mobile morrem. O usuário chega à página do produto, adiciona um item ao carrinho, avança para o checkout e então precisa digitar o endereço completo num teclado touchscreen de 6 polegadas. Se ele errar o código postal, usar o formato errado para o número da casa ou simplesmente desistir, você perde uma venda que já estava ganha.
O autocomplete de endereço resolve isso. Depois da implementação, o preenchimento do endereço deixa de exigir 25 ou mais caracteres digitados em vários campos e passa a exigir poucas teclas e um toque. O usuário começa a digitar o nome da rua, vê uma sugestão correspondente em meio segundo, toca nela, e o endereço inteiro (rua, número, cidade, código postal, país) é preenchido de forma automática e correta. As entregas com falha causadas por erros de digitação diminuem. O abandono do checkout diminui. E, o mais importante, cada pedido passa a ter coordenadas verificadas e geocodificadas, que seus sistemas de logística e roteamento podem usar diretamente.
O Baymard Institute estima o ganho de um checkout mais bem projetado em um aumento de 35,26% na conversão para o grande e-commerce médio, e o benchmark do instituto constatou que 55% dos sites ainda não têm busca de endereço totalmente automática, o que faz do campo de endereço um dos lugares mais fáceis para começar. Quem compra pelo celular sente mais a diferença, já que digitar no teclado do telefone é lento e propenso a erros.
Este tutorial constrói um componente React completo de autocomplete de endereço com a API de Geocodificação MapAtlas, incluindo debounce, navegação por teclado, tratamento de formatos de endereço da UE e integração com formulário. O componente completo tem cerca de 90 linhas.
Por que erros de endereço derrubam conversões
Entregas com falha custam caro em todas as direções: a transportadora cobra uma taxa de reentrega, sua equipe de atendimento lida com a reclamação e a confiança do cliente na sua marca sai abalada. Nos testes de checkout em larga escala do Baymard Institute, 9% dos endereços de entrega digitados manualmente continham erros de digitação ou de grafia.
As causas são previsíveis:
- A digitação no teclado do celular gera mais erros do que no desktop. O corretor automático costuma estragar nomes de ruas e de cidades.
- Os formatos de código postal variam de país para país. Um cliente alemão que digita um código de 5 dígitos num campo que espera o formato britânico (AN NAA) vai disparar um erro de validação.
- A ordem entre rua e número muda entre os países da UE. Na Alemanha e nos Países Baixos, o número vem depois do nome da rua. Na França, vem antes. Formulários de preenchimento manual raramente orientam o usuário corretamente.
- Apartamento e andar não têm formato padronizado. Cada usuário digita do jeito que lhe parece natural, o que muitas vezes não bate com o que a transportadora espera.
O autocomplete contorna a maioria desses problemas ao retornar um objeto de endereço estruturado e já validado. O usuário seleciona o que pretende, e seu formulário recebe o formato correto.
O endpoint de autocomplete da MapAtlas
O endpoint para sugestões de autocomplete é:
GET https://gateway.mapmetrics-atlas.net/autocomplete/?text={query}&token={YOUR_API_KEY}
Parâmetros opcionais que importam para o e-commerce na UE:
| Parâmetro | Tipo | Descrição |
|---|---|---|
text | string | A consulta parcial de endereço |
focus.point.lon | number | Longitude do usuário (prioriza resultados próximos) |
focus.point.lat | number | Latitude do usuário (prioriza resultados próximos) |
boundary.country | string | Código de país ISO 3166-1 alfa-2 (por exemplo, DE, FR, NL) |
layers | string | Filtra os tipos de resultado: address, street, locality |
size | number | Número de resultados (padrão 10, máximo 20) |
Uma resposta típica:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [4.9041, 52.3676] },
"properties": {
"id": "address:node/1234567",
"label": "Damrak 1, 1012 LG Amsterdam, Netherlands",
"name": "Damrak 1",
"street": "Damrak",
"housenumber": "1",
"postalcode": "1012 LG",
"locality": "Amsterdam",
"region": "North Holland",
"country": "Netherlands",
"country_code": "NL",
"confidence": 0.98
}
}
]
}
Cada resultado volta como uma feature GeoJSON com componentes de endereço estruturados. Seu formulário recebe dados limpos e validados, que você pode inserir diretamente em cada campo ou armazenar como um único objeto junto com as coordenadas, para roteamento e planejamento de entregas.
Construindo o hook de autocomplete em React
Comece extraindo a lógica da API para um hook reutilizável. Isso mantém o componente limpo e permite testar o hook de forma independente.
// hooks/useAddressAutocomplete.js
import { useState, useEffect, useRef } from 'react';
const API_BASE = 'https://gateway.mapmetrics-atlas.net/autocomplete/';
const API_KEY = process.env.NEXT_PUBLIC_MAPATLAS_KEY;
const DEBOUNCE_MS = 300;
const MIN_CHARS = 3;
export function useAddressAutocomplete(countryCode = null) {
const [query, setQuery] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const debounceTimer = useRef(null);
useEffect(() => {
if (query.length < MIN_CHARS) {
setSuggestions([]);
return;
}
clearTimeout(debounceTimer.current);
debounceTimer.current = setTimeout(async () => {
setLoading(true);
setError(null);
try {
const url = new URL(API_BASE);
url.searchParams.set('text', query);
url.searchParams.set('token', API_KEY);
url.searchParams.set('size', '6');
url.searchParams.set('layers', 'address');
if (countryCode) {
url.searchParams.set('boundary.country', countryCode);
}
const res = await fetch(url.toString());
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
setSuggestions(data.features ?? []);
} catch (err) {
setError(err.message);
setSuggestions([]);
} finally {
setLoading(false);
}
}, DEBOUNCE_MS);
return () => clearTimeout(debounceTimer.current);
}, [query, countryCode]);
return { query, setQuery, suggestions, loading, error };
}
O timer de debounce só dispara depois que o usuário para de digitar por 300ms. A verificação MIN_CHARS evita chamadas à API com entradas de 1 ou 2 caracteres, em que os resultados seriam amplos demais para ter utilidade. As duas medidas são essenciais para manter o uso da API (e os custos) proporcional à intenção real do usuário.
O componente de autocomplete
// components/AddressAutocomplete.jsx
import { useState, useRef } from 'react';
import { useAddressAutocomplete } from '../hooks/useAddressAutocomplete';
export function AddressAutocomplete({ onSelect, countryCode, placeholder }) {
const { query, setQuery, suggestions, loading } = useAddressAutocomplete(countryCode);
const [open, setOpen] = useState(false);
const [highlighted, setHighlighted] = useState(-1);
const inputRef = useRef(null);
function handleSelect(feature) {
const p = feature.properties;
setQuery(p.label);
setOpen(false);
setHighlighted(-1);
onSelect({
label: p.label,
street: p.street ?? '',
housenumber: p.housenumber ?? '',
postalcode: p.postalcode ?? '',
locality: p.locality ?? '',
region: p.region ?? '',
country: p.country ?? '',
country_code: p.country_code ?? '',
coordinates: feature.geometry.coordinates, // [lng, lat]
});
}
function handleKeyDown(e) {
if (!open || suggestions.length === 0) return;
if (e.key === 'ArrowDown') setHighlighted(h => Math.min(h + 1, suggestions.length - 1));
if (e.key === 'ArrowUp') setHighlighted(h => Math.max(h - 1, 0));
if (e.key === 'Enter' && highlighted >= 0) handleSelect(suggestions[highlighted]);
if (e.key === 'Escape') setOpen(false);
}
return (
<div style={{ position: 'relative' }}>
<input
ref={inputRef}
type="text"
value={query}
placeholder={placeholder ?? 'Start typing your address...'}
onChange={e => { setQuery(e.target.value); setOpen(true); setHighlighted(-1); }}
onKeyDown={handleKeyDown}
onBlur={() => setTimeout(() => setOpen(false), 150)}
style={{ width: '100%', padding: '10px 12px', fontSize: 16, borderRadius: 6, border: '1px solid #ccc' }}
autoComplete="off"
aria-autocomplete="list"
aria-haspopup="listbox"
aria-expanded={open && suggestions.length > 0}
/>
{loading && (
<span style={{ position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)', fontSize: 12, color: '#888' }}>
Searching…
</span>
)}
{open && suggestions.length > 0 && (
<ul
role="listbox"
style={{
position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 999,
background: '#fff', border: '1px solid #ccc', borderTop: 'none',
borderRadius: '0 0 6px 6px', listStyle: 'none', margin: 0, padding: 0,
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
}}
>
{suggestions.map((feature, i) => (
<li
key={feature.properties.id}
role="option"
aria-selected={i === highlighted}
onMouseDown={() => handleSelect(feature)}
onMouseEnter={() => setHighlighted(i)}
style={{
padding: '10px 12px',
cursor: 'pointer',
fontSize: 14,
background: i === highlighted ? '#f0f7e6' : '#fff',
borderBottom: i < suggestions.length - 1 ? '1px solid #f0f0f0' : 'none',
}}
>
{feature.properties.label}
</li>
))}
</ul>
)}
</div>
);
}
O componente oferece navegação completa por teclado (setas, Enter, Escape), atributos ARIA para compatibilidade com leitores de tela e um atraso de 150ms no blur, para que os cliques do mouse nas sugestões sejam registrados antes de a lista fechar.
Integrando com um formulário de checkout
// pages/checkout.jsx
import { useState } from 'react';
import { AddressAutocomplete } from '../components/AddressAutocomplete';
export default function CheckoutPage() {
const [address, setAddress] = useState({
street: '', housenumber: '', postalcode: '',
locality: '', country: '', coordinates: null,
});
function handleAddressSelect(selected) {
setAddress(selected);
// Coordinates are available for routing/delivery estimation
console.log('Delivery coordinates:', selected.coordinates);
}
return (
<form>
<h2>Delivery address</h2>
<AddressAutocomplete
onSelect={handleAddressSelect}
countryCode="NL" // Restrict to Netherlands, remove for EU-wide
placeholder="Start typing your street address..."
/>
{/* Show structured fields after selection, allow manual edits */}
{address.street && (
<div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 8, marginTop: 12 }}>
<input value={address.street} onChange={e => setAddress(a => ({ ...a, street: e.target.value }))} placeholder="Street" />
<input value={address.housenumber} onChange={e => setAddress(a => ({ ...a, housenumber: e.target.value }))} placeholder="No." style={{ width: 80 }} />
<input value={address.postalcode} onChange={e => setAddress(a => ({ ...a, postalcode: e.target.value }))} placeholder="Postal code" />
<input value={address.locality} onChange={e => setAddress(a => ({ ...a, locality: e.target.value }))} placeholder="City" />
</div>
)}
<button type="submit" style={{ marginTop: 16 }}>
Continue to payment
</button>
</form>
);
}
Mostrar campos individuais editáveis depois do autocomplete é importante para a acessibilidade e para casos de borda. O endereço real da porta do usuário pode incluir um número de apartamento ou um código de acesso que o resultado geocodificado não traz. O autocomplete preenche o endereço-base validado, e o usuário completa o resto.
Particularidades dos formatos de endereço da UE
Cada país da UE tem convenções de endereço que afetam tanto a exibição quanto a ordem dos campos do formulário:
Alemanha (DEU): rua primeiro, número depois. Hauptstraße 42, 10115 Berlin. A propriedade housenumber da API segue corretamente a rua nos resultados alemães.
França (FRA): número antes da rua. 42 rue de Rivoli, 75001 Paris. A propriedade label retorna os endereços no formato adequado a cada país.
Países Baixos (NLD): os códigos postais holandeses têm 4 dígitos + 2 letras maiúsculas, separados por um espaço: 1012 LG. Valide esse formato se você divide o código postal para o seu sistema de envio.
Bélgica (BEL): regiões bilíngues podem retornar endereços em francês ou em holandês, dependendo do município.
A API de Geocodificação MapAtlas trata todos esses casos corretamente no campo label (legível, no formato de cada país) e também retorna os campos estruturados street, housenumber e postalcode, para que você possa montar layouts de formulário específicos por país, se precisar.
Para validar em massa bases de endereços já existentes, por exemplo para limpar um CRM legado antes de lançar um serviço de entregas, veja Como usar a API de Geocodificação para validar 10.000 endereços em massa.
Considerações de desempenho
A implementação acima envia uma chamada à API cada vez que o usuário pausa a digitação por 300ms, e não uma por caractere, então um endereço típico custa um punhado de requisições. Para um site de e-commerce com volume relevante de checkouts, coloque um proxy no servidor na frente da API de Geocodificação, para que sua chave de API nunca apareça no código do cliente:
// pages/api/autocomplete.js (Next.js API route)
export default async function handler(req, res) {
const { text, countryCode } = req.query;
const url = new URL('https://gateway.mapmetrics-atlas.net/autocomplete/');
url.searchParams.set('text', text);
url.searchParams.set('token', process.env.MAPATLAS_KEY); // Server-side env var
url.searchParams.set('size', '6');
url.searchParams.set('layers', 'address');
if (countryCode) url.searchParams.set('boundary.country', countryCode);
const response = await fetch(url.toString());
const data = await response.json();
res.json(data);
}
Depois, atualize o hook para chamar /api/autocomplete em vez de chamar a API MapAtlas diretamente. Essa abordagem também permite adicionar cache de requisições na camada de edge (Vercel Edge Functions, Cloudflare Workers) para reduzir as chamadas à API em consultas comuns.
Veja a página de preços da MapAtlas para os valores atuais da API de Geocodificação e os limites do plano gratuito. Na maioria das implementações de e-commerce, o uso do autocomplete cabe com folga no plano gratuito durante o desenvolvimento.
Resumo
Um único campo de autocomplete de endereço pode mover de forma significativa a taxa de conversão do seu checkout. A implementação é simples: um fetch com debounce para a API de Geocodificação MapAtlas, um pequeno componente dropdown com navegação por teclado e uma integração com o formulário que preenche os campos estruturados a partir do resultado selecionado.
Decisões principais:
- Debounce de 300ms para evitar chamadas excessivas à API com quem digita rápido.
- Exija 3 caracteres antes de disparar as requisições.
- Restrinja por país se você conhece a geografia da sua base de usuários, porque isso melhora bastante a relevância dos resultados.
- Sempre permita a edição manual dos campos preenchidos pelo autocomplete, para números de apartamento, códigos de acesso e correções.
- Use um proxy para a chave de API no servidor em produção, para não expor credenciais nos bundles do cliente.
Para a sua primeira integração de mapa junto ao campo de endereço, veja Como adicionar mapas interativos ao seu site e mostre o local de entrega numa página de confirmação.
Crie sua chave de API MapAtlas gratuita para começar a construir. A API de Geocodificação está incluída no plano gratuito, sem cartão de crédito.
Perguntas frequentes
Como o autocomplete de endereço melhora a conversão no checkout?
O preenchimento do endereço é uma das partes do checkout que mais exigem digitação, especialmente no celular. O autocomplete reduz isso a poucas teclas seguidas de um toque, evita erros de formato que causam falhas na entrega e elimina a dúvida sobre se o endereço foi digitado corretamente. Nos testes de checkout em larga escala do Baymard Institute, 9% dos endereços de entrega digitados manualmente continham erros de digitação ou de grafia, justamente o tipo de erro que uma sugestão selecionada evita.
A API de Geocodificação MapAtlas suporta formatos de endereço europeus?
Sim. A API lida com formatos específicos da UE, incluindo o número da casa depois do nome da rua na Alemanha, os arrondissements franceses, os códigos postais holandeses (quatro dígitos mais duas letras) e formatos de endereço multilíngues em todos os estados-membros da UE. Os resultados são retornados como GeoJSON com componentes de endereço estruturados.
Como evitar chamadas excessivas à API durante o autocomplete?
Faça debounce do handler de input em 250-300ms, para enviar uma requisição só depois que o usuário parar de digitar. Defina também um mínimo de caracteres (3-4 caracteres) antes de disparar as requisições. Juntas, essas duas medidas reduzem bastante o volume de requisições em comparação com disparar a cada tecla: um endereço de 20 caracteres digitado em poucas rajadas gera um punhado de requisições em vez de 20.

