Optimizing Mapbox GL JS Performance with Vector Tile Filtering
Learn how to use Mapbox GL JS vector tile filtering and the Expression API to reduce render overhead and create high‑performance, dynamic geospatial dashboards.
16 Sept 2025, 09:29 UTC

The Performance Cost of Redundant Layers
When building a geospatial dashboard, a common instinct is to create a separate layer for every category of data. For example, if you are mapping city infrastructure, you might create one layer for "Water Pipes," another for "Electric Lines," and a third for "Gas Mains." While intuitive, this approach forces the browser to manage multiple render passes and increases the complexity of your layer stack.
The more efficient path is to use a single vector tile source containing all infrastructure data and use client‑side filtering. By shifting the logic from "which layer is active" to "which features within the layer are visible," you reduce memory overhead and simplify your code.
Leveraging the Mapbox Expression API
Mapbox GL JS uses a declarative Expression API to handle data styling and visibility. Instead of writing JavaScript loops to hide or show elements, you define a filter property. This filter is processed on the GPU, allowing you to toggle thousands of features instantly without requesting new tiles from the server.
Filtering is most powerful when combined with data‑driven styling. Rather than just hiding features, you can use expressions to change the color or size of a feature based on its attributes. This ensures that the map remains a single, cohesive data source while presenting different visual stories to the user.
Implementation: Dynamic Feature Toggling
To implement this, you need a vector tile source where features share a common attribute (e.g., a category field). The following example demonstrates how to toggle the visibility of specific infrastructure types using the setFilter method.
// Run this in your client‑side JavaScript environment.
// Requires a valid Mapbox access token and an initialized 'map' object.
const infrastructureLayerId = 'city-utilities';
// 1. Add the layer with an initial filter to show everything
map.addLayer({
'id': 'city-utilities',
'type': 'line',
'source': 'utilities-source',
'source-layer': 'utility_lines',
'paint': {
'line-color': ['match', ['get', 'category'],
'water', '#0000FF',
'electric', '#FFFF00',
'gas', '#FF0000',
'#cccccc' // fallback color
],
'line-width': 2
},
'filter': ['==', ['get', 'category'], 'water'] // Initially show only water
});
// 2. Function to dynamically change the filter based on UI input
function updateUtilityFilter(category) {
if (category === 'all') {
// Pass null to remove the filter and show all features in the source layer
map.setFilter(infrastructureLayerId, null);
} else {
// Update filter to show only the selected category
map.setFilter(infrastructureLayerId, ['==', ['get', 'category'], category]);
}
}
Verification and Risks
- Verification: Open the browser's Network tab. When calling
setFilter, you should see zero new network requests for tiles. The change should be instantaneous. - Permissions: Ensure your Mapbox API token has access to the specific tileset used in the
source. - Risk: Using
setFilteron an extremely large number of features (millions) in a single tile can cause a momentary frame drop on low‑end mobile devices as the GPU re‑calculates visibility.
Trade‑offs: Single Layer vs. Multiple Layers
While filtering is generally superior, there are specific scenarios where discrete layers are necessary. The primary limitation is rendering order (Z‑index). In Mapbox GL JS, layers are rendered in the order they are added. If you use a single layer with a filter, all filtered features share the same Z‑index.
| Scenario | Single Layer + Filter | Multiple Discrete Layers |
|---|---|---|
| Performance | High (GPU accelerated) | Lower (More render passes) |
| Z‑Index Control | None (All features same level) | Precise (Control overlap) |
| Styling | Expression‑based | Static per layer |
If your project requires that "Electric Lines" always appear above "Water Pipes" regardless of the zoom level, you must use separate layers and manage them using the beforeId parameter in addLayer().
Actionable Summary
To optimize your geospatial visualization, audit your current layer stack. If you have multiple layers drawing from the same vector source with different colors but similar geometries, consolidate them into a single layer. Use the ['match', ...] expression for styling and setFilter() for visibility toggles. Only split them into separate layers if you encounter a specific requirement for overlapping priority.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.