Build a web map with Leaflet: troubleshooting guide

Leaflet makes it straightforward to render an interactive map in a browser, but a working prototype can fail for reasons that have little to do with JavaScript syntax. The map container may have no height, the Leaflet CSS or JavaScript may be missing, coordinates may be reversed, a tile URL may be invalid, a vector layer may contain unexpected geometry, or a public tile service may reject production traffic. This troubleshooting guide isolates those failures in a practical order, starting with the smallest known-good map and then adding overlays, data, and interaction.

Leaflet is an open-source JavaScript library for interactive maps. Its core responsibility is browser-side map interaction and layer rendering; it does not provide a universal basemap, geocoder, routing engine, spatial database, or authoritative geographic dataset. That separation is one of Leaflet's strengths, but it also means that a web map can fail at several boundaries: HTML and CSS layout, JavaScript initialization, tile-server access, coordinate reference and coordinate order, GeoJSON structure, browser security, or application state. Leaflet's official quick-start example uses a defined map height, Leaflet CSS and JavaScript, a map instance created with `L.map`, and a tile layer added separately. (leafletjs.com)

How EDK fits

For: Frontend developers, GIS developers, analysts, and technical teams debugging a Leaflet map that is blank, incomplete, visually incorrect, slow, or unreliable in production.

A Leaflet map is a presentation layer for geographic information: it turns coordinates, features, tiles, and user interactions into a view of physical places. That makes it useful at the final stage of a broader world-model workflow, where geographic features have already been found, normalized, measured, filtered, or ranked. The important boundary is that Leaflet itself does not discover or compute everything about the physical world. It displays layers and responds to map events; the underlying data and geographic computations must come from sources such as OpenStreetMap, a spatial database, an API, or another geospatial system. In an E2-oriented workflow, Leaflet could serve as the browser interface for results such as nearby roads, buildings, public assets, waterways, service requests, or change observations, while the finding, joining, ranking, proximity, aggregation, and change-detection work happens in the underlying world model and computation layer.

  • •Use Leaflet to display a filtered set of physical features returned by a geographic search, such as roads, buildings, waterways, public assets, or infrastructure within a user-selected area. The map is the visual inspection surface; the authoritative feature retrieval and filtering remain upstream.
  • •Use Leaflet to inspect computed geographic results such as nearest-feature matches, ranked locations, regional summaries, or detected changes. A popup or side panel can expose the provenance and measurements behind each displayed feature rather than treating the basemap as the analysis itself.
  • •Use Leaflet as a lightweight front end for comparing layers that describe the same place at different times or from different sources, provided each layer has explicit provenance, timestamps, geometry conventions, and licensing information.
Library role
Client-side JavaScript library for interactive maps, layers, controls, and map events.
Basemap responsibility
Leaflet does not provide a universal basemap; you choose and operate within the terms of a tile provider.
Coordinate order in common Leaflet calls
Latitude, longitude: `[lat, lng]`.
Tile URL template
A provider-specific template commonly containing `{z}`, `{x}`, and `{y}` placeholders.
Official reference used here
Leaflet 1.9.4 API reference. (leafletjs.com)

The EDK outcome

Before you start

Basic HTML, CSS, and JavaScript knowledge.

A browser with developer tools, especially the Console and Network panels.

A local development server or deployed test page.

A known-good coordinate pair and a documented tile provider.

A clear understanding of the data source, coordinate order, geometry type, attribution, and service terms for every layer.

Learning objectives

Reduce a failing Leaflet application to a minimal known-good map.

Distinguish layout, JavaScript, tile-service, data, coordinate, and lifecycle failures.

Validate tile URLs, attribution, caching assumptions, and provider restrictions.

Diagnose incorrect marker and GeoJSON positions caused by coordinate-order or reference-system mistakes.

Make map initialization and overlay loading observable and reproducible in production.

Verification

The minimal map renders with a nonzero container height.

The Console contains no initialization error.

At least one tile request succeeds and the provider's attribution is visible.

A known test point renders at the expected location.

The real data response is valid, has expected geometry types, and uses documented coordinate conventions.

The map survives resize, route changes, hidden-to-visible transitions, and a production reload.

The application records enough context to reproduce failures: map state, layer identifiers, provider, data timestamp, and first error.

Troubleshooting

Blank map with no controls: inspect container height, CSS loading, duplicate element IDs, and whether initialization runs after the container exists.

Controls visible but no tiles: inspect tile URL, HTTPS, status codes, provider authorization, referrer behavior, content-security policy, and service restrictions.

`L is not defined`: verify the Leaflet script request, script order, CDN availability, and whether a module or bundler import is required by the application.

Markers in the wrong country: verify `[latitude, longitude]` versus GeoJSON `[longitude, latitude]`, then verify the coordinate reference system and numeric values.

GeoJSON causes an exception: validate JSON syntax, geometry types, null geometries, coordinate nesting, and callbacks that assume properties exist.

Map appears cropped or blank after opening a modal: inspect parent dimensions and call `invalidateSize()` after visibility changes.

Map works locally but not in production: compare deployed asset paths, CSP, CORS, proxy behavior, referrer policy, authentication, and environment-specific tile configuration.

Browser becomes slow with many features: reduce the query extent, simplify geometry, cluster point data, use server-side filtering or tiling, and avoid creating a large number of individual DOM-heavy markers.

Tiles are intermittently unavailable: check provider availability and usage policy, cache behavior, request volume, and whether the application depends on a best-effort public service for a critical workflow.

Attribution disappears: keep provider attribution in the visible map UI and test it at mobile widths and under responsive controls.

Next steps

Move provider configuration, attribution, and maximum zoom into one documented configuration module.

Add automated smoke tests that verify the map container, Leaflet initialization, a known tile request, and one known feature.

Attach provenance and timestamps to geographic overlays so users can distinguish a rendered map from authoritative or computed results.

For larger datasets, evaluate vector tiles, server-side filtering, clustering, WebGL-based rendering, or a spatial API instead of pushing every feature into the browser.

Read the complementary production guide for deployment, provider selection, and operational design, then use the vector-tile and GeoPandas troubleshooting guides when the failure is in rendering scale or upstream spatial analysis.

What you need before debugging

Before debugging a complex application, create a minimal reproduction in a plain HTML file or a small isolated route. Keep the first version limited to one map container, one Leaflet version, one tile layer, and a fixed geographic center. This establishes whether the failure is in Leaflet initialization or in your framework, build system, data pipeline, or custom controls.

Use a browser with developer tools. The Console identifies JavaScript exceptions such as `L is not defined`, while the Network panel shows whether Leaflet assets and tile images return successfully. Confirm that the page is served over HTTP during development rather than opened directly with a `file://` URL when your data or tiles require browser requests.

  • •A reproducible browser error and the first relevant console message.
  • •The Leaflet version loaded by the page; the official 1.9.4 reference documents the stable API covered by the examples used here. (leafletjs.com)
  • •A map container with an explicit height and a unique identifier.
  • •A known coordinate pair and a known tile-service URL.
  • •A clear distinction between basemap tiles, application overlays, and computed geographic results.

Step 1: reduce the map to a known-good baseline

Replace the application temporarily with the smallest complete page. The order matters: load Leaflet CSS, create a container with height, load Leaflet JavaScript, then initialize the map after the container exists. The following example follows the structure of the official Leaflet quick start while using HTTPS for the OpenStreetMap tile URL. (leafletjs.com)

  • •If the page shows a completely blank region with no controls, inspect the container dimensions first.
  • •If the browser reports `L is not defined`, the JavaScript file did not load, loaded after your initialization code, or was blocked.
  • •If controls appear but tiles do not, initialization succeeded and the problem is probably the tile request, attribution configuration, provider policy, or network environment.
  • •If the map appears only after resizing the browser, call `map.invalidateSize()` after the containing element becomes visible or changes dimensions. This is especially relevant for tabs, dialogs, accordions, and dynamically sized panels.

Step 2: diagnose tiles, attribution, and provider behavior

A Leaflet map can be functioning correctly while its basemap is unavailable. Inspect one failed tile request in the Network panel. A `404` usually indicates a malformed URL or unsupported path. A `401` or `403` points to authentication, referrer, origin, or provider-policy issues. A request that remains pending can indicate connectivity, a blocked domain, or a service-side problem. Mixed-content warnings occur when an HTTPS page requests an HTTP tile URL.

For OpenStreetMap's standard raster tiles, the current policy specifies `https://tile.openstreetmap.org/{z}/{x}/{y}.png`, visible attribution, a valid identifying request context, normal browser referrers, respect for cache headers, and no bulk downloading or offline prefetching. The service is best-effort and does not provide an SLA. Do not treat the public tile server as an unlimited production CDN. (operations.osmfoundation.org)

  • •Check the exact request URL, response status, response headers, and browser-referrer behavior.
  • •Confirm that the attribution is visible and not hidden under another control.
  • •Do not use a tile URL copied from an unrelated map application unless its provider terms permit reuse.
  • •Do not solve a slow public service by adding aggressive prefetching or disabling browser caching.
  • •If the basemap is optional, test the application with a temporary local or approved alternative layer so overlay debugging can continue.

Step 3: validate coordinates, layers, and GeoJSON

Leaflet's geographic APIs use latitude and longitude in the order `[lat, lng]`. A common failure is passing GeoJSON coordinates directly into a marker without accounting for GeoJSON's `[longitude, latitude]` order. The result may be a feature rendered in the wrong country, outside the visible map, or at an invalid latitude. Test one known point before loading a large dataset.

For a GeoJSON overlay, validate the response before adding it to the map. Confirm that the response is valid JSON, has `type: "FeatureCollection"` or a supported GeoJSON object type, contains geometries rather than null values where expected, and uses the coordinate reference system your application assumes. Modern GeoJSON workflows generally use WGS 84 longitude-latitude coordinates, but your upstream service should state its convention explicitly rather than relying on guesswork.

  • •Log the first feature and its coordinates before rendering the full collection.
  • •Use `map.getBounds()` or a layer's bounds to check whether the data is outside the initial viewport.
  • •Check for `null` geometries and empty coordinate arrays.
  • •Use a stable identifier in feature properties so popup and click behavior can be traced back to source records.
  • •Avoid adding tens of thousands of individual DOM-heavy markers without clustering, simplification, server-side filtering, or a different rendering strategy.

Step 4: troubleshoot interaction, layout, and production differences

When the map renders but interaction fails, isolate event handling from data loading. Attach a simple click listener to the map and show the clicked coordinates. If that works, the problem is probably in the overlay event handler, popup content, or application state. Leaflet's official quick start demonstrates map click events and popups as separate concerns, which is a useful debugging pattern. (leafletjs.com)

For maps inside hidden elements, initialize after the element is visible or call `invalidateSize()` after it becomes visible. For maps inside flexbox or grid layouts, inspect the computed height of every parent. A child with `height: 100%` cannot acquire useful height if its ancestors have no defined height.

  • •Verify the map container has nonzero width and height at initialization and after route changes.
  • •Ensure the map is created once per DOM element and destroyed or detached correctly when a component unmounts.
  • •Test tile and data requests from the deployed origin, not only from localhost.
  • •Keep popups concise and escape or sanitize untrusted text before inserting HTML.
  • •Record map center, zoom, layer identifiers, data timestamp, and provider configuration when diagnosing user reports.

Workflow

01

1. Capture the failure before changing code

Record the browser URL, Leaflet version, expected behavior, actual behavior, first console error, and whether the failure affects the basemap, overlays, controls, or interaction. Take a screenshot and preserve one failed network request. Avoid changing several variables at once.

02

2. Run a minimal baseline

Create a page with a fixed-height `#map` element, Leaflet CSS and JavaScript, one fixed center and zoom, and one tile layer. Confirm that the baseline works before restoring framework components, asynchronous data, custom styles, and application state.

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" crossorigin="" />
<style>#map { height: 360px; }</style>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" crossorigin=""></script>
<script>
  const map = L.map('map').setView([51.505, -0.09], 13);
  L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '&copy; OpenStreetMap contributors'
  }).addTo(map);
</script>
03

3. Inspect tile requests independently

Use the Network panel to inspect a requested tile. Confirm the URL template, HTTPS, status code, response type, referrer behavior, and provider requirements. If using OpenStreetMap's standard tiles, compare the implementation with the current tile policy rather than assuming that a tutorial snippet is a production entitlement.

// Keep provider configuration centralized and replaceable.
const tileUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
const tiles = L.tileLayer(tileUrl, {
  maxZoom: 19,
  attribution: '&copy; OpenStreetMap contributors'
});
tiles.addTo(map);
04

4. Add one known feature and verify coordinates

Add a single marker or simple shape. Confirm that the expected location is visible at the current zoom. If the feature is misplaced, inspect whether the source uses GeoJSON `[longitude, latitude]` while the Leaflet call expects `[latitude, longitude]`.

const point = [51.505, -0.09];
L.marker(point)
  .addTo(map)
  .bindPopup('Known test point')
  .openPopup();
05

5. Add the real data incrementally

Load one feature, then a small collection, then the full result. Log the response status, content type, feature count, first geometry, coordinate bounds, and data timestamp. Add styling and event callbacks only after the raw geometry renders.

fetch('/data/features.geojson')
  .then(response => {
    if (!response.ok) throw new Error(`Data request failed: ${response.status}`);
    return response.json();
  })
  .then(data => {
    console.log({ type: data.type, featureCount: data.features?.length });
    L.geoJSON(data).addTo(map);
  })
  .catch(error => console.error('GeoJSON load failed', error));
06

6. Test responsive and lifecycle behavior

Resize the browser, switch tabs, open and close the containing panel, navigate between application routes, and reload on a deep link. Confirm that the container has dimensions, that initialization happens once, and that the map is resized after hidden containers become visible.

// Run after a hidden panel, tab, modal, or layout transition becomes visible.
requestAnimationFrame(() => map.invalidateSize());
07

7. Reproduce the deployed failure

Run the same checks from the production origin. Compare asset paths, content-security policy, CORS behavior, referrer policy, authentication, proxy headers, provider configuration, and data versions. Add structured logging for map initialization, layer loading, and failed requests.

Questions teams ask

Why is my Leaflet map completely blank?

Check the map container's computed height first; a container with no height produces no visible map. Then check the Console for `L is not defined` or other initialization errors, verify that Leaflet CSS and JavaScript loaded, and confirm that the initialization code runs after the container exists. If controls appear but the basemap is absent, inspect tile requests separately.

Why do my markers appear in the wrong place?

Check coordinate order. Leaflet map centers and marker positions commonly use `[latitude, longitude]`, while GeoJSON coordinates use `[longitude, latitude]`. Also verify that the coordinates are in the expected geographic reference system and that the map is not displaying a feature outside the current viewport.

Can I use OpenStreetMap tiles in a production Leaflet application?

Not automatically or without constraints. The OpenStreetMap Foundation's tile policy requires the correct HTTPS tile URL, visible attribution, identifiable requests, normal caching behavior, and no bulk downloading or offline prefetching. The service is best-effort and has no SLA. For substantial or specialized production use, choose an appropriate provider or operate a permitted tile stack. (operations.osmfoundation.org)

Why does my map work in development but fail after deployment?

Compare the deployed page's Console and Network output with localhost. Look for content-security-policy violations, incorrect asset paths, mixed-content requests, CORS failures, authentication or referrer differences, reverse-proxy changes, and component lifecycle differences. Test tile and data URLs from the deployed origin.

Why do overlays disappear when I switch tabs or open a modal?

The map may have been initialized while its container was hidden or had zero dimensions. After the container becomes visible, call `map.invalidateSize()` and confirm that its parent elements have defined dimensions. Also verify that the overlay was not removed during a component rerender.

Keep exploring

Sources

More in Guides