Map Performance: Choosing Between GeoJSON and Vector Tiles in Mapbox GL JS
A technical guide on choosing between GeoJSON and Vector Tiles in Mapbox GL JS to prevent browser lag and optimize.
15 May 2026, 09:21 UTC

When building data-heavy maps, the most common bottleneck is not the rendering engine itself, but how the browser handles data transfer and parsing. Many developers start with GeoJSON because it is easy to fetch, but quickly find their maps stuttering or the browser freezing during pans once the dataset grows beyond a few thousand points. To maintain a fluid 60 FPS experience, you must decide whether to process data on the client side via GeoJSON or offload the lifting to the server using Vector Tiles (MVT).
The Source-Layer Architecture
Mapbox GL JS operates on a decoupled architecture. The source holds the raw data (coordinates and properties), while the layer defines how that data looks (color, opacity, line width). Because these are separate, you can update the visual style without re-uploading the data. For example, if you want to highlight points based on a category, you should use a layer filter rather than modifying the GeoJSON array itself.
When to use GeoJSON
GeoJSON is ideal for small, dynamic datasets that change frequently based on user interaction. Since the browser holds the entire file in memory, you can perform instant CRUD (Create, Read, Update, Delete) operations without waiting for network round-trips.
The Limitation: The browser must parse the entire JSON string at once. A 10MB GeoJSON file will likely cause the main thread to block while the string is converted into JavaScript objects. Use GeoJSON only for datasets under 5MB or those where the data is updated in real-time streams.
When to use Vector Tiles (MVT)
Vector tiles solve the scale problem by breaking data into chunks based on zoom level and geographic coordinates. The browser only downloads the data currently visible in the viewport. This makes it the only viable option for datasets with hundreds of thousands or million of features.
The Trade-off: Vector tiles require a pre-processing pipeline. You cannot simply point Mapbox to a raw database query; you must tile the data (using tools like Mapbox Tilesets or a self-hosted tiles server), which adds complexity to your backend architecture.
Practical Example: High-Performance Filtering
Instead of re-filtering your data in JavaScript—which is slow—use the setFilter method. This executes the logic on the GPU, keeping the map responsive even with large data layers.
// Assume we have a GeoJSON source named 'traffic-data'
map.addSource('traffic-data', {
'type': 'geojson',
'data': '/api/traffic.json'
});
map.addLayer({
'id': 'traffic-layer',
'type': 'circle',
'source': 'traffic-data',
'paint': {
'circle-color': '#ff0000',
'circle-radius': 5
}
});
// To filter for only areas with 'congestion' level > 80
// This happens on the GPU without re-fetching data
map.setFilter('traffic-layer', ['>', ['get', 'congestion'], 80]);
How to Verify Performance
To determine if your data choice is failing you, use the Chrome DevTools Performance tab:
- Look for 'Long Tasks': If you see red bars exceeding 50ms during data load, your GeoJSON is likely too large for the main thread.
- Monitor Memory: Use the Memory tab to see if heap usage spikes and stays high; consider switching to vector tiles to reduce overhead.
- Test FPS: Rapidly zoom the map while filters are active. If the frame rate drops below 30 FPS, your expressions may be too complex for the device hardware.
Start with GeoJSON for speed of development. The moment your dataset exceeds 5MB or you notice main-thread lag during initial loads, migrate to a vector tile pipeline to keep your application fluid.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.