On EUDR work I've had to show tens of thousands of farm plots on one map, with validation status, filters, and selection. The first naive version froze the browser. Here's what fixed it.
The problem
- Big GeoJSON takes time to download, parse (main thread), and upload to the GPU.
- React re-renders can re-add map layers or re-pass huge objects.
- Complex polygons with thousands of vertices each slow down everything.
1. Keep heavy data out of React state
const mapRef = useRef<maplibregl.Map | null>(null); const [selectedId, setSelectedId] = useState<string | null>(null); // Update styles, not data, when selection changes useEffect(() => { mapRef.current?.setFeatureState( { source: "plots", id: selectedId ?? "" }, { selected: true }, ); }, [selectedId]);
Use feature state and filter expressions for highlights and filters instead of rebuilding the data.
2. Use vector tiles for big static layers
Pre-tile large datasets so the browser only loads what's visible at the current zoom:
- Generate tiles with tippecanoe
- Serve as PMTiles from static storage, with no tile server needed
- Use MapLibre's PMTiles protocol to read them
3. Simplify geometry per zoom
At zoom 6 you don't need 2,000 vertices per plot. Simplify for low zooms (tippecanoe does this automatically; for dynamic data, simplify server-side with PostGIS ST_SimplifyPreserveTopology).
4. Use deck.gl for very large or dynamic data
deck.gl renders on the GPU and handles millions of points well. It works with MapLibre as an overlay or interleaved layer.
import { MapboxOverlay } from "@deck.gl/mapbox"; import { ScatterplotLayer } from "@deck.gl/layers"; const overlay = new MapboxOverlay({ layers: [ new ScatterplotLayer({ id: "sensors", data: points, // typed arrays are faster still getPosition: (d) => [d.lon, d.lat], getRadius: 30, getFillColor: [234, 88, 12], }), ], }); map.addControl(overlay);
5. Move parsing off the main thread
Parse and transform large datasets in a Web Worker, then pass the result (ideally as transferable typed arrays).
6. Fetch only what's visible
For dynamic data, query by bounding box and zoom from the API, backed by a spatial index (PostGIS guide).