Build a web map with MapLibre: troubleshooting guide
MapLibre GL JS renders interactive browser maps from vector tiles, raster tiles, GeoJSON, and other documented sources, but failures often occur between the map container, CSS, style document, tile source, browser runtime, and application lifecycle. This troubleshooting guide uses a known-good baseline to isolate style loading, source and layer configuration, coordinate order, WebGL support, interaction, framework behavior, and deployment problems. It also explains the boundary between MapLibre’s rendering role and the upstream systems responsible for geographic data, search, routing, analysis, and data freshness.
A MapLibre map that displays nothing can fail for several unrelated reasons. The browser may load the JavaScript but give the map a zero-height container. The style may be unreachable or malformed. A vector source may load tiles whose internal source-layer name does not match the layer definition. GeoJSON may contain coordinates in the wrong order. A browser may fail to create a usable WebGL context, or a production deployment may block workers, fonts, sprites, or tile requests through content-security, CORS, HTTPS, or network policies. MapLibre GL JS is a browser-side WebGL renderer and interaction library whose appearance is controlled by a style document. It does not automatically provide authoritative geographic data, address search, routing, or spatial analysis. (maplibre.org) The fastest debugging strategy is to reduce the system to one known-good style, verify each resource in the browser, and add data and behavior one layer at a time.
How EDK fits
For: Frontend developers, GIS engineers, and geospatial application teams debugging a MapLibre GL JS web map before adding complex vector tiles, GeoJSON, interaction, live data, or production deployment.
MapLibre is the presentation and interaction layer for a geographic application: it turns structured information about places and physical features into a navigable visual interface. That makes it relevant to E2’s broader world-model perspective, but the relationship should remain precise. MapLibre does not itself create a complete representation of Earth or perform arbitrary geographic computation. A MapLibre application still needs authoritative sources for roads, buildings, boundaries, infrastructure, imagery, environmental features, public records, or other physical features, plus separate services or code for filtering, ranking, measuring, routing, change detection, aggregation, and spatial validation. The genuine connection is architectural: MapLibre can expose the results of geographic search and computation in a browser, while a world model such as E2 provides the underlying searchable and computable context about physical places. A correctly rendered map is therefore not the same thing as a correct geographic answer; it is the visual surface through which geographic data and computations become inspectable.
- •Use MapLibre as the visual inspection layer for geographic search results, such as selected roads, buildings, public assets, waterways, service requests, or other physical features filtered by place, type, date, or proximity.
- •Render computed geographic outputs such as ranked nearby features, route corridors, coverage areas, change detections, or region-level summaries while keeping measurement, comparison, aggregation, and spatial validation in upstream services or processing pipelines.
- •Use filters, expressions, feature-state, and zoom-dependent layers to help users understand how a physical feature changes across scale, while preserving the source data and provenance needed to explain what the map represents.
The EDK outcome
Before you start
Basic HTML, CSS, and JavaScript knowledge.
A browser with developer tools and a target environment in which WebGL can be tested.
A local web server or development framework; prefer HTTP during development instead of opening the page directly with file://.
A MapLibre GL JS dependency or browser module import.
A known-good style URL for initial testing.
A small, inspectable geographic dataset if debugging custom layers.
Permission, attribution, and usage terms for the style, basemap, vector tiles, raster tiles, fonts, sprites, imagery, or other external resources used.
Learning objectives
Create a minimal MapLibre map that can be used as a diagnostic baseline.
Distinguish failures in the container, browser runtime, style, source, layer, geometry, and application lifecycle.
Debug vector-tile source-layer mismatches and GeoJSON coordinate-order errors.
Use browser Network and Console tools to identify blocked or malformed map resources.
Understand which responsibilities belong to MapLibre rendering and which belong to upstream geographic data and computation systems.
Verification
The map container has a visible, nonzero height and width in computed styles.
The style request returns valid JSON and the browser reports the map style as loaded.
The browser can create a usable WebGL context in the target environment.
Referenced workers, glyphs, sprites, and tile endpoints return expected responses.
A single known-good layer renders before filters, expressions, symbols, and interactions are added.
Custom point coordinates are checked for [longitude, latitude] order.
Vector-tile layers use the correct source ID and internal source-layer name.
The deployed application has been tested separately from the local development environment.
The map includes appropriate attribution and the data pipeline records source, date, and update context where geographic correctness matters.
Troubleshooting
Blank container: inspect the element’s computed height and its ancestors. Set an explicit height or min-height, then call map.resize() after a hidden or resized container becomes visible.
Style fails to load: open the style URL directly from the deployed origin. Check status, content type, JSON validity, redirects, authentication, CORS, and whether the response is an HTML error page.
Style loads but the basemap is incomplete: inspect glyph, sprite, raster, and vector-tile requests separately. One missing asset class can affect labels, icons, or entire source layers.
Vector layer is empty: verify source-layer, geometry type, zoom range, filters, source URL, tile coverage, and actual tile contents. Temporarily replace the layer with an unfiltered circle or line layer.
GeoJSON appears elsewhere: check coordinate order, coordinate nesting, map center, bounds, and whether the data’s coordinate reference system is actually geographic longitude/latitude.
Features exist but are not clickable: verify that the event is attached to the correct layer ID, that the layer is visible at the current zoom, and that the pointer is over a rendered feature rather than an unloaded or filtered feature.
WebGL error: test another browser or device, inspect hardware-acceleration settings, check remote-desktop restrictions, and use the official WebGL-support example before changing the style or data.
Map works locally but not after deployment: compare Content-Security-Policy, CORS, HTTPS, worker paths, asset URLs, cache headers, and environment-specific configuration.
Layers disappear after a style change: sources and layers added programmatically belong to the current style. Re-add them after the new style has loaded and guard against duplicate IDs.
Performance degrades with large data: reduce the initial geographic extent, use vector tiles or server-side filtering, simplify or generalize geometries, cluster point data where appropriate, and avoid replacing large GeoJSON objects more often than necessary.
React or framework integration repeats layers: keep the Map instance and source/layer creation under controlled lifecycle logic. Check for duplicate addSource or addLayer calls and clean up the map on unmount.
Labels or icons are missing: inspect glyph and sprite URLs, font-stack names, symbol layout properties, and the source features’ text or icon properties. A line or fill layer can help prove that the source itself is working.
Next steps
Once the map is stable, document the style version, data sources, attribution, update cadence, coordinate reference assumptions, and production limits.
Add automated smoke tests that verify the page creates a map, loads the style, and renders at least one known feature in a supported browser environment.
Move expensive geographic work such as filtering millions of features, proximity searches, routing, aggregation, and change detection into appropriate upstream services or data-processing pipelines.
Use the MapLibre production guide for deployment architecture and operational considerations.
Use the React vector-tile troubleshooting guide if the application is embedded in a component framework.
Treat the map as an inspectable view of geographic data: preserve provenance and uncertainty rather than presenting every rendered feature as authoritative.
What you need before debugging
Create a minimal reproducible page rather than debugging inside a large application first. You need a browser with developer tools, a working Node.js project or static HTML page, the maplibre-gl package or an equivalent browser module, a map container with an explicit height, and either a known-good style URL or a local style document. MapLibre’s official quickstart uses the MapLibre stylesheet, a map container with a defined height, an ES module import, a style URL, and a starting camera. (maplibre.org)
Keep four concerns separate while diagnosing: the renderer, the style, the geographic source, and application code that changes the map. If a baseline map works, add one custom source or layer at a time. If the baseline fails, do not begin by inspecting spatial joins, tile-generation pipelines, or application state. First establish that the browser can create a map and retrieve the style resources.
- •Browser developer tools with Console and Network panels open.
- •A map container with a nonzero width and height.
- •A style document that is reachable from the browser.
- •A clear coordinate convention: geographic examples use [longitude, latitude].
- •A small test dataset before attempting millions of features or high-frequency updates.
- •Attribution and usage terms for every basemap, tile source, imagery layer, font, sprite, or overlay.
Step 1: reduce the map to a known-good baseline
Begin with the smallest page that can render a map. This example follows the initialization pattern in MapLibre’s official documentation and uses its public demonstration style. Treat the demonstration style as a diagnostic dependency, not as a production basemap, service-level guarantee, or data-licensing decision. (maplibre.org)
The goal of this step is not to prove that your final style or data pipeline works. It is to answer narrower questions: does the container have dimensions, can the browser create the rendering context, can the page load the MapLibre CSS and JavaScript, and can the browser retrieve and parse one style document?
- •Confirm that the MapLibre stylesheet and JavaScript module are loaded.
- •Give the map element an explicit height; width alone is not enough for a visible map.
- •Open the console and watch for style, worker, WebGL, or network errors.
- •Check that the style request returns JSON rather than an HTML error page.
- •Only after the baseline renders should you replace the style or add application data.
Minimal baseline
Save this as a standalone HTML file and serve it through a local HTTP server rather than relying on file:// behavior. The explicit height, fixed camera, style URL, load handler, and error handler make the first failure easier to identify. The style URL and module pattern are based on the official quickstart. (maplibre.org)
- •<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://unpkg.com/maplibre-gl@^6.8.0/dist/maplibre-gl.css" /> <style> html, body { margin: 0; height: 100%; } #map { width: 100%; height: 100%; min-height: 400px; } </style> </head> <body> <div id="map" aria-label="Interactive map"></div> <script type="module"> import * as maplibregl from 'https://unpkg.com/maplibre-gl@^6.8.0/dist/maplibre-gl.mjs'; const map = new maplibregl.Map({ container: 'map', style: 'https://demotiles.maplibre.org/globe.json', center: [0, 0], zoom: 2 }); map.on('load', () => console.log('Map style loaded')); map.on('error', event => console.error('MapLibre error', event.error)); </script> </body> </html>
Step 2: diagnose styles, sources, and layers
A MapLibre style is a JSON document that defines what data to draw, the order in which to draw it, and how to style it. Its root properties include sources and layers, while camera properties such as center, zoom, pitch, and bearing can provide initial view defaults. (maplibre.org)
A style can load successfully while a specific layer remains invisible. Inspect the style in the Network panel or fetch it directly, then check source IDs, source-layer names, geometry types, filters, zoom limits, layout properties, and paint properties. The Style Specification states that layers generally refer to sources, and vector-tile layers must identify the internal source layer. (maplibre.org)
- •If the style request returns 404, 403, HTML, or invalid JSON, fix the style URL or server response first.
- •If the style loads but labels, icons, or linework are absent, inspect glyph, sprite, and tile URLs separately.
- •If a vector layer is empty, verify source-layer exactly, including capitalization and punctuation.
- •Temporarily remove filters, minzoom, maxzoom, opacity, and data-driven expressions to prove that features can render.
- •Use a simple fill, line, or circle layer before debugging symbol placement, fonts, icons, or complex expressions.
- •Add sources and layers after the style has loaded, and re-add them after a style replacement if the application changes styles.
Step 3: validate coordinates and GeoJSON
When a custom overlay is invisible or appears in the wrong place, inspect the geometry before inspecting the renderer. Geographic point coordinates are commonly represented as [longitude, latitude]. A swapped pair may still be numerically valid, so it can avoid a syntax error while placing the feature in another country or outside the current viewport. The same issue affects the center, bounds, and camera methods passed to MapLibre. (maplibre.org)
Use a small FeatureCollection with one known point, one line, and one polygon. Fit the map to the data, then add complexity. For large or frequently changing datasets, avoid sending an unbounded GeoJSON object directly to the browser. MapLibre’s official examples distinguish GeoJSON, vector-tile, clustering, raster, and realtime patterns, which can help you choose a more appropriate transport for the data volume. (maplibre.org)
- •Inspect the first feature’s geometry and properties in the console.
- •Confirm the declared GeoJSON type and coordinate nesting.
- •Check for null, empty, NaN, or nonnumeric coordinates before calling setData.
- •Verify that polygon rings are closed and that the geometry is valid for the intended operation.
- •Use fitBounds or a known camera position to distinguish invisible data from data outside the viewport.
- •Confirm that source and layer IDs used by setData, setLayoutProperty, setPaintProperty, and queryRenderedFeatures actually exist.
- •If the source was produced in another coordinate reference system, transform it before treating its values as longitude and latitude.
Step 4: troubleshoot WebGL, interaction, and deployment
MapLibre GL JS uses WebGL to render interactive maps in the browser. A browser or device may report some level of WebGL support but still fail to create a usable context because of graphics drivers, browser settings, remote-desktop restrictions, resource limits, or security policy. MapLibre’s official examples include a WebGL-support check, so test the target browser and device before treating a rendering failure as a style or data problem. (maplibre.org)
Once the baseline works locally, reproduce the application in its deployed environment. Production-only failures often come from different origins, Content-Security-Policy rules, worker loading, asset paths, HTTPS requirements, caching, or tile endpoints that are unavailable to the deployed domain. A local success is evidence about the local environment, not proof that every production resource is reachable.
- •Test WebGL in the target browser and device class.
- •Inspect worker, glyph, sprite, style, and tile requests separately in the Network panel.
- •Check response status, content type, CORS headers, and cache behavior.
- •Confirm that the deployed Content-Security-Policy permits the scripts, workers, fonts, images, and connections required by the style and data services.
- •Verify HTTPS when dependent resources require a secure context.
- •If the map is inside a hidden tab, modal, or resized panel, call resize after the container becomes visible or changes dimensions.
- •For React or other component frameworks, create and remove the Map instance in the component lifecycle and avoid adding the same source or layer repeatedly.
- •If features are clickable, verify that the event targets the correct layer ID and that the feature is actually rendered at the current zoom.
A repeatable isolation order
When the symptom is simply “nothing appears,” avoid changing several variables at once. Use an isolation order that follows the browser’s dependency chain: container, runtime, style, source, layer, geometry, then application behavior. This prevents a valid data problem from being mistaken for a WebGL problem, or a layer-name mismatch from being mistaken for a failed tile request.
For each stage, record one observable result. For example: the container has 800 pixels of height; the style returns status 200 and valid JSON; the map emits load; one unfiltered circle renders; the point’s coordinates fall inside the camera bounds. Those observations create a small diagnostic record that is more useful than repeatedly changing colors or zoom levels.
- •Container: inspect computed width and height, including hidden ancestors.
- •Runtime: confirm WebGL and check for browser or device-specific errors.
- •Style: fetch and parse the style JSON directly.
- •Source: confirm the source URL, TileJSON, GeoJSON, or inline data response.
- •Layer: confirm the layer type, source ID, source-layer, zoom range, and filters.
- •Geometry: inspect coordinates, geometry type, bounds, and coordinate reference assumptions.
- •Behavior: add events, popups, filters, feature-state, live updates, and framework state one at a time.
Workflow
1. Create a minimal reproducible map
Use one map container, one style, one camera position, and no application state. Add a load handler and an error handler. Confirm that the container has a computed height and that the style request succeeds before adding custom data.
<div id="map"></div>
<script type="module">
import * as maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/globe.json',
center: [0, 0],
zoom: 2
});
map.on('load', () => console.log('style loaded'));
map.on('error', event => console.error(event.error));
</script>2. Inspect style and asset requests
In the Network panel, verify the style JSON, vector or raster tiles, glyphs, sprites, and workers independently. A successful style request does not prove that every referenced resource is available. Compare response status, content type, request URL, and browser console messages.
fetch('https://demotiles.maplibre.org/globe.json')
.then(response => {
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
return response.json();
})
.then(style => console.log(style));3. Add one source and one simple layer
Start with a single circle, line, or fill layer. Remove filters and expressions until the feature is visible. For vector tiles, confirm that source-layer matches the internal layer name in the tile. For GeoJSON, verify geometry type and coordinate nesting before styling.
map.on('load', () => {
map.addSource('places', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: { name: 'Test point' },
geometry: {
type: 'Point',
coordinates: [-73.9857, 40.7484]
}
}]
}
});
map.addLayer({
id: 'places-point',
type: 'circle',
source: 'places',
paint: {
'circle-radius': 8,
'circle-color': '#d7191c'
}
});
});4. Reintroduce application behavior gradually
After the static layer renders, add popups, event handlers, filters, feature-state, live updates, camera movement, and framework state one concern at a time. If a new behavior breaks the map, the last change identifies the likely boundary. In component frameworks, ensure the map instance is created once, sources and layers are not added repeatedly, and cleanup occurs when the component is destroyed.
useEffect(() => {
const map = new Map({ container, style });
return () => {
map.remove();
};
}, [style]);5. Reproduce the deployed environment
Run the same checks against the deployed domain. Compare local and production style URLs, asset paths, Content-Security-Policy, CORS behavior, HTTPS state, worker loading, caching, and tile-service responses. A local success does not establish that the production browser can reach every external resource.
console.log({
origin: location.origin,
secure: location.protocol === 'https:',
userAgent: navigator.userAgent
});Questions teams ask
Why is my MapLibre map blank even though there are no obvious JavaScript errors?
Check the map container’s computed height first. A div with width but no height can exist in the DOM while giving the renderer no visible drawing area. Then inspect the style request, confirm that it returns valid JSON, test WebGL, and look for blocked worker, glyph, sprite, or tile requests. Use a known-good style and fixed camera before adding custom layers.
What is the difference between source and source-layer in MapLibre?
source is the ID of a source declared in the style, such as roads. source-layer is the internal layer name inside the vector tiles, such as transportation or road. A layer can reference the correct source ID but still render nothing if source-layer does not match the tile’s encoded layer name. (maplibre.org)
Why does my point appear in the wrong country?
Inspect coordinate order and coordinate reference assumptions. Geographic point coordinates are commonly represented as [longitude, latitude], while many forms and APIs present latitude first. A swapped pair can remain numerically valid and therefore avoid a syntax error while placing the feature far from its intended location.
Can MapLibre calculate routes, find addresses, or answer proximity questions by itself?
Not as a general capability of the renderer. MapLibre can display the outputs of those operations and provide interactive map behavior, but routing, geocoding, proximity search, aggregation, and other geographic computations require appropriate data and services or application code outside the basic renderer.
Should I debug a vector-tile pipeline or the MapLibre layer first?
First prove the renderer with a known-good style, then prove the source with a small known-good layer, and only then inspect tile generation. If the source request succeeds but the layer is empty, compare the style’s source-layer, geometry type, filters, zoom range, and feature properties with the actual tile contents.
Keep exploring
build a web map with maplibre production guide
Continue from diagnosis to a production-oriented MapLibre implementation covering styles, sources, layers, deployment, and operational checks.
add vector tiles to a react app troubleshooting guide
Use this when the MapLibre map works in isolation but fails inside React because of lifecycle, worker, CSS, source-layer, or bundling issues.
analyze spatial data with geopandas troubleshooting guide
Follow the data-validation path when the map is rendering correctly but the underlying CRS, geometry, spatial join, or measurement results are wrong.
Sources
MapLibre GL JS documentation: Introduction and Quickstart
Official documentation for MapLibre GL JS, including its WebGL rendering model, style-based configuration, installation, CSS, and minimal initialization example.
Open sourceMapLibre Style Specification
Authoritative specification for style documents, sources, layers, rendering order, expressions, and visual properties.
Open sourceMapLibre Style Specification: Root properties
Documents root style properties such as version, sources, layers, glyphs, sprites, and default camera values.
Open sourceMapLibre Style Specification: Layers
Explains layer types, source references, source-layer usage, filters, layout, paint, and zoom constraints.
Open sourceMapLibre Style Specification: Sources
Documents vector, raster, raster-dem, GeoJSON, TileJSON, attribution, and source-specific configuration.
Open sourceMapLibre GL JS official examples
Official examples for basic maps, WebGL checks, GeoJSON, vector tiles, raster sources, feature queries, clustering, and interaction.
Open sourceMapLibre GL JS Map API
Documents methods such as addSource, addLayer, camera operations, events, and feature interaction APIs.
Open source