Every service business has a service area. The HVAC company that covers Denver metro but not Boulder. The roofer who works the Pikes Peak corridor from Colorado Springs up to Monument and out to Black Forest, but not Pueblo. The plumber whose ten-truck fleet covers Salt Lake County but stops short of Provo. The boundaries of where a business will and will not drive a truck are a meaningful sales conversation: a customer who finds you online needs to know within five seconds whether you cover their address before they bother filling out a form.

The tool that conveys this best is a map. Not a screenshot from Google Maps with a few highlighted neighborhoods. Not a static list of ZIP codes. A real, interactive, zoomable map with every covered city pinned, the shop's location marked, and a click on each pin linking to the relevant service-area page. The stack I use to build these is two open-source projects that cost nothing to use: Leaflet and OpenStreetMap.

Why not just use Google Maps embed

The Google Maps JavaScript API is the default option that most people reach for first. It is also the wrong default for small business service-area maps in 2026, for three concrete reasons.

It costs money. Google's Maps API has a free monthly tier of about 28,000 map loads, after which the cost is $7 per 1,000 loads. For a small site this is unlikely to be a budget problem on day one. It becomes a budget problem the month a press placement or a viral local mention sends 50,000 visitors to the service area page in a week. The bill arrives 30 days later. I do not want to be in the position of asking a client whether I should remove the map to control cost.

It requires an API key. Google's Maps API key has to be billing-enabled and credit-card-attached, even for the free tier. The key gets restricted by referrer or IP, but it is still a credential that lives in the page's JavaScript. The setup work is not hard, but it is process I would rather not impose on every client.

It pulls a third-party tracker. Loading the Google Maps JS adds Google's tracking infrastructure to the page. For a privacy-conscious client (or a client subject to GDPR-style consent rules even if they do not realize it yet), this is a problem. The agency stack already minimizes third-party JS aggressively; opting back into Google for the map would undo that.

The alternative — Leaflet plus OpenStreetMap tiles — has none of those tradeoffs.

What Leaflet is

Leaflet is a small open-source JavaScript library that renders interactive maps. The library is about 42KB minified and gzipped, has been around since 2011, is maintained by the OpenStreetMap community, and is used in production at Facebook, Pinterest, Etsy, and (until they switched to their own renderer) GitHub. The license is BSD-2-Clause, which means free for any use including commercial, with no attribution requirement in the JavaScript itself.

The library handles the parts of an interactive map that are the same in every implementation: pan, zoom, marker placement, popup rendering, layer toggling, mobile touch handling, retina-display tile loading, accessibility focus management. What it does not include is the map data itself. The tiles — the actual rendered images of streets and parks and landmarks — come from a separate provider. For my agency stack, that provider is OpenStreetMap.

What OpenStreetMap is

OpenStreetMap (OSM) is a community-maintained open dataset of the physical world. Volunteers contribute road centerlines, building footprints, business locations, park boundaries, and the like; a global cleaning pipeline merges and validates them; the result is a dataset comparable to Google Maps in coverage and, in some regions (cycling infrastructure especially), better.

The OSM data is licensed under the Open Database License (ODbL), which means free to use including commercially, with attribution to "© OpenStreetMap contributors." Most map renderings include the attribution as a small line of text in the bottom-right corner of the map, which Leaflet does automatically. The attribution is the only obligation; there is no fee, no API key, and no rate limit beyond the polite-use guidelines for the public tile servers.

For higher-traffic sites I sometimes route the tiles through a paid OSM proxy (Stadia Maps, Mapbox's OSM-based tiles, or self-hosted via OpenMapTiles), but the public tile servers are sufficient for any small business service-area map I have built. The traffic profile of a service-area page is low enough that the courtesy use guidelines never become a concern.

The technical setup, end to end

The full stack for one of these maps is about thirty lines of HTML and JavaScript. The flow:

  1. Load Leaflet's CSS and JavaScript from a CDN. Both files are tiny and cached aggressively across the web.
  2. Add a single <div> with a unique ID to the page where the map should render. Set its height in CSS.
  3. In a small script block, instantiate a Leaflet map centered on the business's primary city, with an appropriate zoom level (10 for metro coverage, 11-12 for tighter local).
  4. Add an OSM tile layer to the map. Three lines of JavaScript.
  5. For each city or neighborhood the business covers, add a marker at that location's lat/lng. The lat/lng comes from a hand-curated list inside the page's JavaScript or from a build-time data file.
  6. Bind a popup to each marker with the city name, the ZIP code(s) it covers, and a link into the corresponding service-area page on the site.
  7. Add a distinct marker for the business's shop or office location, styled differently from the area markers (a custom icon, a different color, or a label).

The result is an interactive map with the business's coverage geography rendered on top of OpenStreetMap's road layer, every covered area pinned, every pin linked to the city's dedicated service-area page, and the shop pinned distinctly so visitors can see where the operation is based.

The data file pattern

The lat/lng list for the covered areas could live as inline JavaScript in the page, but the cleaner pattern in my stack is to put it in a build-time data file. For Eleventy, that means a JavaScript module under src/_data/ that exports an array of { name, zip, lat, lng } objects.

The advantage of the data file is that the same dataset can drive multiple things on the site:

  • The interactive map markers.
  • The static list of service areas in the footer.
  • The individual /service-area/colorado-springs/ pages, generated via Eleventy pagination.
  • The local-SEO content on each city page.

The lat/lng values themselves come from manually looking up each city or neighborhood in OpenStreetMap and copying the coordinates from the URL bar (OSM puts them in the URL as you pan). The whole exercise for a 12-area service map takes about twenty minutes the first time. Updates (a new city added, a coverage change) are a one-line edit to the data file.

Performance characteristics

The performance of a Leaflet+OSM map is genuinely fast. The Leaflet library itself is around 42KB after compression. The OSM tiles are PNG images cached aggressively by the browser; a typical service-area map at zoom level 11 renders five to twelve tiles depending on screen size, around 100-300KB total on first load and effectively instant on subsequent visits.

The map does not block page rendering. I load Leaflet with the defer attribute and instantiate the map only after the page is interactive. The result is that the service-area page itself loads at 90+ Lighthouse Performance even with the map present; the map fades in over the next half-second after the page is usable.

Compared to a Google Maps embed, the Leaflet+OSM stack is roughly half the JavaScript weight, has zero analytics overhead, and never sends a request to a Google domain. The visitor's browser session stays inside the agency's infrastructure and Cloudflare's edge cache.

Accessibility and mobile

Leaflet handles keyboard navigation out of the box: tab into the map, arrow keys to pan, plus and minus for zoom. Markers are real DOM elements that can take focus, and popups have proper close-on-Escape behavior. The mobile touch handling is solid — pinch to zoom, drag to pan, tap a marker for the popup. None of this required any custom code in my implementation.

The one accessibility consideration worth calling out: the Leaflet container needs an explicit role="region" and aria-label describing what the map shows ("Map of [Business] service area covering [Region]"). Leaflet doesn't add these automatically. They go in the HTML markup of the map's <div>.

The next post in this thread covers the customer-facing side: what a real interactive service-area map actually does for a small business's marketing, in concrete terms.

Share this article
Service-area map included

Every service business site I build gets a real map.

Not a screenshot from Google Maps and not a static SVG. A real interactive map showing every neighborhood, ZIP code, or city you cover, with a pin on the shop and links into the relevant city pages. Part of the standard plan, no API fees.

Start a Conversation → See what's included