Build a web map with Leaflet: production guide for interactive maps

Leaflet is a provider-agnostic JavaScript library for building interactive web maps. A useful production implementation requires more than creating a map object: you must size the map container, load Leaflet CSS and JavaScript in the correct order, choose a tile provider, preserve attribution, manage feature data, validate coordinate order, and plan for tile-service limits. This guide builds a small map with Leaflet 1.9.4, OpenStreetMap raster tiles, markers, a popup, and a GeoJSON layer while distinguishing Leaflet’s rendering role from the geographic data and computation performed elsewhere.

A Leaflet map is a browser interface to geographic information. Leaflet handles map interaction and rendering layers, while tile providers, GeoJSON files, APIs, and spatial databases supply the underlying representation of places and features. That separation is one of Leaflet’s strengths: you can change the basemap provider or add your own data without changing the core map interaction model. It also creates production responsibilities. A map can render successfully while still using an unsuitable tile service, displaying incorrect coordinates, exposing unsafe popup HTML, or implying geographic precision that the source data does not support. This guide starts with a deliberately small implementation and then extends it toward a maintainable application.

How EDK fits

For: Web developers, GIS developers, and location-data practitioners who need a practical starting point for an interactive Leaflet map and want to understand the operational boundaries around basemaps, GeoJSON, attribution, and geographic data quality.

Leaflet is a useful presentation layer for making a subset of the physical world visible and interactive, but it is not itself a world model, geocoder, routing engine, spatial database, or authoritative source of geographic facts. Its role is to display geographic features and expose interactions such as pan, zoom, click, popups, and layer visibility. That makes Leaflet relevant to the broader problem of finding and understanding places: a developer can use it to present roads, buildings, parks, streams, service locations, incidents, or other features that have already been sourced and modeled elsewhere. The computational work remains explicit. Filtering features, measuring distance, ranking nearby places, joining incidents to neighborhoods, detecting change, or routing across roads requires appropriate geographic data and computation outside the basic Leaflet rendering loop. In an E2-oriented workflow, Leaflet could serve as a lightweight map interface over a broader representation of Earth: the map would show selected physical features, while search, spatial relationships, aggregation, and analysis would come from the underlying world model and its computation layer. That connection is genuine, but Leaflet should not be described as providing those capabilities by itself.

  • •Use Leaflet as a focused visual interface for inspecting a selected slice of a larger physical-world dataset, such as roads, parks, waterways, buildings, public assets, or service locations. The selection and geographic truth should come from the underlying data system; Leaflet renders the result and exposes feature interaction.
  • •Use a Leaflet map to review the output of geographic computation: for example, display features returned by a proximity search, show a filtered set of roads or facilities, or inspect the result of a region-based analysis. Leaflet presents the answer but does not replace the spatial query, measurement, ranking, or aggregation that produced it.
Library
Leaflet, an open-source JavaScript library for mobile-friendly interactive maps. (leafletjs.com)
Version used in this guide
Leaflet 1.9.4, matching the official quick-start example and 1.9.4 API reference. (leafletjs.com)
Basemap example
OpenStreetMap standard raster tiles, subject to the OpenStreetMap Foundation Tile Usage Policy. (operations.osmfoundation.org)
Feature format
GeoJSON for geometry plus feature properties, with Leaflet support for standard GeoJSON geometry types. (leafletjs.com)

The EDK outcome

Before you start

Basic familiarity with HTML, CSS, and JavaScript.

A browser with developer tools.

A local HTTP server or development server for serving the page.

A defined tile-provider choice and an understanding of its attribution and usage policy.

A small test dataset or GeoJSON feature whose coordinates and meaning you can verify.

Learning objectives

Create a working Leaflet map with a correctly sized container and version-pinned assets.

Add a raster tile layer with visible attribution and provider-aware operational safeguards.

Display markers and GeoJSON features with safe, useful interaction.

Diagnose coordinate-order, loading, tile, popup, and data-volume problems.

Understand which responsibilities belong to Leaflet and which belong to the underlying geographic data and computation systems.

Verification

The map container has a nonzero computed height.

Leaflet CSS and JavaScript load without console errors.

The initial map view is centered on the intended geographic location.

Tile requests use the selected provider’s documented URL and HTTPS where required.

Attribution is visible and not hidden by custom controls or responsive layout.

The known test feature appears in the expected location.

GeoJSON points use longitude, latitude coordinate order.

Popup content from external or user-controlled data is not inserted as unsafe raw HTML.

The application handles failed tiles, failed data requests, empty results, and malformed features.

The chosen tile provider’s terms permit the intended traffic, caching, commercial use, and offline behavior, if applicable.

Troubleshooting

Blank map: inspect the computed height of the map container first. A missing or zero height is a common initialization problem. Then verify that Leaflet CSS and JavaScript loaded and that the map element ID matches the ID passed to L.map(). (leafletjs.com)

Tiles do not appear: inspect network responses, confirm the tile URL template, check HTTPS and attribution configuration, and review the provider’s usage policy. Do not assume a tile service is available for unlimited public or commercial traffic. (operations.osmfoundation.org)

Features are displaced: check whether a value was passed as [latitude, longitude] where GeoJSON expects [longitude, latitude], or vice versa. Test with one known coordinate before loading the full dataset.

Popups show unexpected markup: treat external properties as untrusted. Build a DOM element with textContent or sanitize HTML with an appropriate library, following Leaflet’s GeoJSON security guidance. (leafletjs.com)

The browser becomes slow: reduce the number of features, simplify geometries, filter by viewport or category, use server-side queries, or move to a tiled representation. Rendering fewer features is usually more reliable than asking the browser to parse a very large response.

The map shows outdated or inconsistent information: attach source and update metadata to features, define refresh behavior, and distinguish the time the map was rendered from the time the underlying feature was observed or published.

Attribution is missing on mobile: inspect responsive CSS, custom controls, and stacking order. Attribution must remain visible and should not be hidden behind another interface element when using services that require it. (operations.osmfoundation.org)

Next steps

Replace the prototype basemap with a provider selected for your traffic, license, coverage, and availability requirements.

Add a layer control for separate themes such as roads, facilities, waterways, incidents, or boundaries.

Serve GeoJSON from a geographic API that supports bounding-box, category, time, or proximity filtering.

Move expensive spatial operations such as nearest-feature search, buffers, intersections, routing, and aggregation into a spatial computation layer.

Add automated tests for coordinate order, popup escaping, empty responses, invalid geometry, and provider configuration.

Document data provenance, attribution, update schedules, known coverage gaps, and the difference between what is displayed and what has been computed.

What you need before writing code

You need a basic HTML page, a browser with developer tools, and a way to serve the page over HTTP during development. A static file server is preferable to opening the document directly from the filesystem because production maps commonly load external scripts, stylesheets, tiles, or GeoJSON resources. You also need to decide whether the map is a prototype, an internal tool, or a public application. That decision affects the tile provider, expected traffic, caching strategy, attribution, privacy review, and availability requirements.

This example pins Leaflet 1.9.4 because that is the version used by the official quick-start example and documented by the current 1.9.4 API reference. If you use another release, review the matching reference rather than assuming every option or behavior is identical. (leafletjs.com)

  • •HTML page with a map container.
  • •Leaflet CSS and JavaScript loaded in the documented order.
  • •A defined height for the map container.
  • •A basemap provider whose terms, attribution, rate limits, and availability fit the application.
  • •Geographic data in a format your application can validate and serve, such as GeoJSON.
  • •A plan for coordinate reference systems, coordinate order, source freshness, and error handling.

Step 1: create a minimal Leaflet map

Create an index.html file with a map container and initialize Leaflet after the container and Leaflet script are available. The map container must have an explicit height; otherwise the map may initialize but occupy no visible space. The following example follows the structure of Leaflet’s official quick start and uses the documented OpenStreetMap tile URL and attribution format. (leafletjs.com)

  • •Use HTTPS for the Leaflet assets and tile requests.
  • •Keep the map element’s height in CSS rather than relying on content to size it.
  • •Keep attribution visible inside the map interface.
  • •Do not copy the OpenStreetMap tile configuration into a high-volume production system without reviewing the tile policy.

Step 2: add features and interaction

Leaflet’s core map becomes useful when it displays features from the physical world. Markers work well for individual points such as facilities, incidents, stops, or inspection locations. Polylines can represent roads, paths, or routes, while polygons can represent parks, service areas, parcels, or administrative boundaries. Popups provide context, but treat popup content as an output boundary: if values come from users or third-party data, do not insert untrusted strings as raw HTML.

Leaflet’s GeoJSON layer accepts standard GeoJSON geometry types and can use feature properties for styling, filtering, and popups. The official GeoJSON example also warns that popup strings are interpreted as HTML and recommends sanitizing untrusted content or constructing DOM elements with textContent. (leafletjs.com)

  • •Validate that GeoJSON coordinates use longitude, latitude order, as required by GeoJSON.
  • •Use feature properties for labels and classification, but escape or safely construct popup content.
  • •Use a filter when the browser should display only a subset of features.
  • •Use different layer groups when users need to toggle roads, facilities, water, incidents, or other themes.
  • •Avoid loading very large unfiltered GeoJSON files into the browser; generalize, tile, paginate, or query selectively when volume grows.

Step 3: make geographic behavior explicit

A map can look plausible while representing the wrong place. The most common early error is coordinate order: GeoJSON coordinates are written as [longitude, latitude], while Leaflet map views and many marker constructors are commonly written as [latitude, longitude]. Establish one conversion boundary and test it with a known location before loading real data.

Also distinguish display coordinates from measurement coordinates. Web maps commonly use Web Mercator tile schemes for display, but distances, areas, and other measurements may require a suitable projected coordinate system or geodesic method. Leaflet does not turn a visual layer into a measurement engine. If the application needs nearest-feature searches, buffers, polygon intersections, route calculations, or regional summaries, perform those operations in an appropriate spatial service or analysis library and then send the result to Leaflet for display.

  • •Test one known point before rendering a complete dataset.
  • •Record source, timestamp, identifier, and relevant accuracy or status fields.
  • •Do not infer that visual overlap means real-world coincidence without checking the data and coordinate systems.
  • •Keep geographic computation on the server or in a spatial analysis layer when it is too large, sensitive, or complex for the browser.
  • •Use stable layer and feature identifiers so user interaction remains understandable after data refreshes.

Step 4: prepare for production operation

Production readiness is mostly about boundaries around the map. Choose a tile provider that matches expected traffic and commercial or public-use requirements. Confirm attribution placement, provider terms, caching behavior, geographic coverage, and whether the provider supports the zoom levels and jurisdictions your users need. OpenStreetMap’s standard tile policy requires visible attribution and prohibits bulk downloading, prefetching, and offline use on that service. (operations.osmfoundation.org)

Build loading and failure states. A tile provider can be temporarily unavailable, a GeoJSON endpoint can return malformed data, or a browser can block a request because of a cross-origin policy. The user should see whether the basemap failed, the operational data failed, or the map has simply not finished loading.

  • •Pin and review the Leaflet version used by the application.
  • •Keep tile-provider configuration replaceable rather than burying it throughout the codebase.
  • •Monitor tile and data-request failures separately.
  • •Test mobile layout, keyboard interaction, slow networks, and empty result sets.
  • •Document attribution, data licenses, refresh schedules, and known coverage gaps.
  • •Avoid exposing sensitive coordinates or personal information in public popups and URLs.

Workflow

01

Create the page and include Leaflet

Create index.html with a map container, include Leaflet’s CSS in the head, include Leaflet’s JavaScript after the CSS, and define a visible height for #map. The official quick start uses Leaflet 1.9.4 from the unpkg CDN. For a production application, pin the version and review your organization’s dependency and content-security policies.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Leaflet web map</title>
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRC9tD/miZyoHS5obTRR9BMY=" crossorigin="">
  <style>
    #map { height: 480px; }
  </style>
</head>
<body>
  <main>
    <h1>Places on a map</h1>
    <div id="map" aria-label="Interactive map"></div>
  </main>
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
  <script src="app.js"></script>
</body>
</html>
02

Initialize the map and add a basemap

In app.js, create the map, set its initial view, and add a tile layer. Replace the example tile service when your traffic, licensing, availability, or offline requirements require another provider. Keep attribution visible and accurate.

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; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
03

Add a marker and safe popup

Add an individual feature and bind a popup. For trusted, static content, a short HTML string may be sufficient. For untrusted or externally supplied values, construct a DOM node and assign textContent rather than interpolating raw HTML.

const marker = L.marker([51.505, -0.09]).addTo(map);
marker.bindPopup('<strong>Example location</strong><br>Verify this content before using it with external data.');

map.on('click', (event) => {
  const el = document.createElement('div');
  el.textContent = `Clicked at ${event.latlng.lat.toFixed(5)}, ${event.latlng.lng.toFixed(5)}`;
  L.popup().setLatLng(event.latlng).setContent(el).openOn(map);
});
04

Load and filter GeoJSON

Represent features as GeoJSON Features or a FeatureCollection. Remember that GeoJSON point coordinates are [longitude, latitude]. Use properties to label or filter features, and keep source identifiers and timestamps when they are needed for interpretation.

const places = {
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      properties: { name: 'Example park', visible: true },
      geometry: { type: 'Point', coordinates: [-0.10, 51.51] }
    }
  ]
};

L.geoJSON(places, {
  filter: (feature) => feature.properties?.visible === true,
  onEachFeature: (feature, layer) => {
    const el = document.createElement('div');
    el.textContent = feature.properties?.name ?? 'Unnamed feature';
    layer.bindPopup(el);
  }
}).addTo(map);
05

Test the production boundary

Open the map through your local HTTP server and inspect the browser console and network panel. Verify that the map is visible, tiles load, attribution appears, the known test feature is in the correct location, popups are safe, and empty or failed data responses produce understandable behavior. Then test on a slow connection and a small screen before connecting the map to larger or changing datasets.

# Example development server using Python
python3 -m http.server 8000

# Then open in a browser
# http://localhost:8000

Questions teams ask

Can I use Leaflet with a tile provider other than OpenStreetMap?

Yes. Leaflet is provider-agnostic and does not require a particular tile provider. You must independently review the provider’s URL format, attribution requirements, licensing, usage limits, caching rules, availability, and terms. (leafletjs.com)

Why is my Leaflet map blank even though the JavaScript loaded?

First check that the map container exists before initialization and has a defined CSS height. Then check the browser console and network panel for failed Leaflet assets, blocked tile requests, malformed URLs, or cross-origin errors. A missing container height is specifically called out in Leaflet’s quick-start preparation steps. (leafletjs.com)

Why are my GeoJSON features appearing in the wrong place?

Check coordinate order and coordinate reference assumptions. GeoJSON coordinates are written as [longitude, latitude], while Leaflet map views and markers are often expressed as [latitude, longitude]. Also verify that the source data is actually in the geographic coordinate system you expect.

Is Leaflet suitable for rendering millions of features?

The answer depends on geometry complexity, browser resources, viewport size, and interaction requirements, so there is no universal feature-count threshold. For large datasets, prefer server-side filtering, vector or raster tiling, generalization, or a specialized rendering pipeline instead of sending one enormous GeoJSON document to every browser.

Can I use OpenStreetMap’s standard tiles for offline maps?

Not from the standard tile.openstreetmap.org service. The OpenStreetMap Foundation policy prohibits bulk downloading and offline use and requires normal interactive viewing behavior. Use a provider or self-hosted tile system whose terms explicitly permit offline or prefetch workflows. (operations.osmfoundation.org)

Keep exploring

Sources

More in Guides