DEV Community

DropThe
DropThe

Posted on

Leaflet + 440K Markers: How We Built a 4-Tier Hierarchical Map Without Paid APIs

Google Maps API costs $7 per 1,000 loads after the free tier. Mapbox charges per tile request. We render 440,000 cafe markers on CoffeeTrove for $0/month using Leaflet + CARTO tiles + a custom clustering system.

The Problem

Rendering 440K markers on a map at once will crash any browser. Marker clustering libraries like Leaflet.markercluster work for 10K points but choke beyond that. We needed a solution that:

  1. Works at world zoom (showing continents)
  2. Drills down to individual cafes at street level
  3. Loads fast on mobile
  4. Costs nothing

4-Tier Hierarchical System

Instead of client-side clustering, we cluster server-side at 4 zoom levels:

Tier 1: Continents (zoom < 4)

Hardcoded continent centroids with aggregated counts:

const CONTINENT_MAP = {
  'Europe': { lat: 48.5, lng: 10, count: 185000 },
  'North America': { lat: 42, lng: -100, count: 120000 },
  'Asia': { lat: 30, lng: 105, count: 85000 },
  // ...
};
Enter fullscreen mode Exit fullscreen mode

7 bubbles on screen. Instant render.

Tier 2: Countries (zoom 4-6)

SELECT country, AVG(lat) as lat, AVG(lng) as lng, COUNT(*) as count
FROM cafes
GROUP BY country;
Enter fullscreen mode Exit fullscreen mode

~120 bubbles. Still instant.

Tier 3: City Grid Clustering (zoom 7-11)

This is where it gets interesting. We use spatial grid merging:

SELECT 
  ROUND(lat / :grid_size) * :grid_size as grid_lat,
  ROUND(lng / :grid_size) * :grid_size as grid_lng,
  COUNT(*) as count,
  MIN(name) as sample_name
FROM cafes
WHERE lat BETWEEN :south AND :north
  AND lng BETWEEN :west AND :east
GROUP BY grid_lat, grid_lng;
Enter fullscreen mode Exit fullscreen mode

Grid size varies by zoom: 0.5 degrees at zoom 7, down to 0.02 at zoom 11. PostgreSQL handles the aggregation in ~50ms for any viewport.

Tier 4: Individual Pins (zoom 12+)

At street level, show actual cafes. But cap at 18 per viewport:

SELECT name, lat, lng, score, chain_type
FROM cafes
WHERE lat BETWEEN :south AND :north
  AND lng BETWEEN :west AND :east
ORDER BY score DESC
LIMIT 18;
Enter fullscreen mode Exit fullscreen mode

Airbnb-style cards with name, score badge, and chain indicator.

The Stack (Zero API Keys)

Component Cost Alternative
Leaflet Free Google Maps ($7/1K)
CARTO nolabels tiles Free Mapbox ($0.25/1K)
leaflet.heat (heatmap) Free deck.gl
PostgreSQL spatial queries Free Elasticsearch ($$$)

Total: $0/month regardless of traffic.

IP-Based Centering

No geolocation popup on load. We read Vercel's x-vercel-ip-latitude and x-vercel-ip-longitude headers to center the map on the user's approximate location. The "Locate me" button triggers precise GPS only on explicit click.

// Server component reads headers
const lat = parseFloat(headers.get('x-vercel-ip-latitude') || '40.7');
const lng = parseFloat(headers.get('x-vercel-ip-longitude') || '-74.0');
Enter fullscreen mode Exit fullscreen mode

No permission popup. No API key. Works on first load.

Performance

  • Initial load: 180ms (7 continent bubbles)
  • Country zoom: 220ms (120 country bubbles)
  • City zoom: 50-80ms (grid query + render)
  • Street zoom: 30ms (18 individual pins)

Tested on a $200 Android phone over 4G. Smooth at every zoom level.

Try It

The live map is at coffeetrove.com/map. Zoom from world view down to your street. All 440K cafes, zero API costs.

We use similar spatial techniques on DropThe for city comparison pages, and the same Leaflet stack will power Facil.guide's local tech support finder.


CoffeeTrove is open-source coffee discovery. Built with Next.js 16, PostgreSQL, and Leaflet. No paid map APIs.

Top comments (0)