R
Interview Deckreact · next.js · senior/staff
0·0·122
System Design

Frontend Map Rendering — Mapbox e Performance

Renderizar mapas com milhares de markers. Clustering, virtualização de pins, WebGL e lazy loading de tiles.

Maps no frontend: Mapbox GL JS (WebGL, performático, customizável), Google Maps (mais popular, mais pesado), Leaflet (simples, menos features). WebGL = renderização na GPU, suporta milhares de markers a 60fps via GeoJSON layers. Clustering: agrupar markers próximos em zoom out. Tile loading lazy: carrega tiles conforme o viewport muda. Crítico para apps de delivery, ride-sharing, logística.

Problema
Usar `<Marker>` do React para 500 pins no mapa = 500 elementos no DOM = jank em zoom/pan. Cada Marker é um overlay HTML, não renderizado pelo WebGL. Resultado: mapa congela.
Solução
Para muitos markers estáticos: GeoJSON Layer no Mapbox (renderizado via WebGL, suporta milhões). Para markers interativos com React: virtualizar, mostrar só os visíveis no viewport. Clustering com supercluster ou Mapbox cluster source. Para 1-10 markers interativos: `<Marker>` HTML está bem. Regra: > 50 markers → GeoJSON/WebGL.
Tip
Lazy loading do bundle do mapa (Mapbox GL JS tem ~250kb gz). Usar dynamic import ou Next.js `dynamic(() => import(...), { ssr: false })`. O mapa precisa de um container com height definido — sem isso, Mapbox renderiza com 0px de altura. Invalidar o mapa com `map.resize()` quando o container muda de tamanho (sidebar abre/fecha, fullscreen).
// react-map-gl (wrapper React do Mapbox) com GeoJSON source
import Map, { Source, Layer, Marker } from 'react-map-gl'

function RideMap({ drivers, surgeZones }: MapProps) {
  const mapRef = useRef<MapRef>(null)

  // GeoJSON para muitos drivers (renderizado via WebGL)
  const driversGeoJSON: GeoJSON.FeatureCollection = useMemo(() => ({
    type: 'FeatureCollection',
    features: drivers.map(d => ({
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [d.lng, d.lat] },
      properties: { id: d.id, status: d.status }
    }))
  }), [drivers])

  // Atualizar posição sem re-render React
  const updateDriverPosition = useCallback((driverId: string, pos: Position) => {
    const map = mapRef.current?.getMap()
    const source = map?.getSource('drivers') as GeoJSON.GeoJSONSource
    // Atualiza direto no Mapbox (GPU), sem useState
    source?.setData(driversGeoJSON)
  }, [driversGeoJSON])

  return (
    <Map
      ref={mapRef}
      mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_TOKEN}
      initialViewState={{ longitude: -46.63, latitude: -23.55, zoom: 13 }}
      style={{ width: '100%', height: '500px' }}
    >
      {/* Drivers como WebGL layer — suporta milhares */}
      <Source id="drivers" type="geojson" data={driversGeoJSON} cluster clusterRadius={50}>
        <Layer id="clusters" type="circle" filter={['has', 'point_count']}
          paint={{ 'circle-color': '#3fb6f0', 'circle-radius': 20 }} />
        <Layer id="driver-points" type="symbol" filter={['!', ['has', 'point_count']]}
          layout={{ 'icon-image': 'car-icon' }} />
      </Source>

      {/* Surge zones como polígono */}
      <Source id="surge" type="geojson" data={surgeZones}>
        <Layer id="surge-fill" type="fill"
          paint={{ 'fill-color': '#fb923c', 'fill-opacity': 0.2 }} />
      </Source>
    </Map>
  )
}
Q.Como atualizar a posição de um motorista em tempo real no mapa sem re-renderizar o React?
A.Guardar referência ao objeto Marker ou Layer do Mapbox e atualizar via API imperativa do Mapbox (sem React re-render). `markerRef.current.setLngLat([lng, lat])` para Markers. Para Source GeoJSON: `map.getSource('drivers').setData(newGeoJSON)` — Mapbox só atualiza os dados na GPU, sem tocar no DOM. Isso mantém animações suaves a 60fps.
Q.Como implementar clustering eficiente?
A.Usar Mapbox Cluster Source: `type: 'geojson', cluster: true, clusterRadius: 50`. Mapbox calcula clusters no worker thread. Para interação: ao clicar num cluster, zoom para mostrar os items individuais com `map.flyTo`. Para clusters customizados em React: supercluster library + calcular clusters no useMemo quando viewport muda (bounds + zoom).
Q.Como lidar com geofences e polígonos no mapa?
A.Mapbox Fill Layer para polígonos (surge pricing zones, delivery areas). GeoJSON Polygon ou MultiPolygon como source. Para interatividade: `map.on('click', 'geofence-layer', handler)` para detectar clicks. Para verificar se um ponto está dentro de um polígono no cliente: turf.js `booleanPointInPolygon()`. Para polígonos dinâmicos (desenhados pelo usuário): mapbox-gl-draw plugin.
120/122← →