How to Add Interactive Maps to Your Website | JavaScript Tutorial (2026)
Tutorial

How to Add Interactive Maps to Your Website | JavaScript Tutorial (2026)

Add interactive maps to your website in minutes. Step-by-step JavaScript tutorial covering markers, popups, geocoding search, and React/Next.js integration with MapAtlas.

MapAtlas Team8 min read
#maps#javascript#api#react#next.js#geocoding

Every logistics dashboard, real estate listing, and food delivery app has one thing in common: a map. If your website doesn't have one yet, you're leaving context on the table. Context that helps users understand location, proximity, and spatial relationships at a glance.

This tutorial walks you through adding a fully interactive map to any JavaScript project. You'll start with a rendered map, layer on markers and popups, wire up address search with the Geocoding API, and finish with a production-ready React component that drops cleanly into Next.js.

Here's what you'll build by the end:

  • A live vector map authenticated with your API key
  • Clickable markers with custom popup content
  • A geocoding-powered address search function
  • A reusable React component with proper cleanup and Next.js SSR handling
  • A checklist of performance optimizations for production

Prerequisites

Before you start, make sure you have:

  • A MapAtlas API key (sign up free, no credit card required). This single key authenticates every MapAtlas service: tiles, geocoding, and routing.
  • A JavaScript project. Plain HTML, React, Vue, or Svelte all work.
  • Node.js 18+ if you're installing via npm.

Step 1: Install the MapAtlas SDK

Pull the SDK into your project with npm:

npm install @mapmetrics/mapmetrics-gl

If you're working with a plain HTML page instead, drop the CDN links into your <head>:

<link
  rel="stylesheet"
  href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css"
/>
<script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>

Don't skip the CSS import. Without it, map controls and popups render unstyled. Functional, but visually broken.

Step 2: Create a Map Container

The SDK fills whatever element you point it at, so you need a <div> with an explicit height. This is the single most common setup mistake: if the container has height: 0, the map initializes but stays invisible.

<div id="map" style="width: 100%; height: 500px;"></div>

A fixed pixel value or viewport unit (100vh, 50vh) both work. Percentage heights only work if the parent element also has a defined height.

Step 3: Render Your First Interactive Map

Three lines of configuration are all it takes: a container, a style URL with your API key, and a starting position.

import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const map = new mapmetricsgl.Map({
  container: 'map',
  style: 'https://tiles.mapatlas.eu/styles/basic/style.json?key=YOUR_API_KEY',
  center: [4.9041, 52.3676], // [longitude, latitude]
  zoom: 12,
});

Open the page and you'll see a vector map you can drag, scroll, and pinch to navigate. Tiles load on demand from MapAtlas servers, so there's no heavy upfront download.

Choosing a Map Style

Swap the path segment in the style URL to change the look entirely:

StyleURL PathBest for
Basic/styles/basic/style.jsonGeneral-purpose apps
Bright/styles/bright/style.jsonData visualization overlays
Dark/styles/dark/style.jsonDashboards, night mode, analytics

Quick win: Use the Dark style for admin panels and tools used in low-light environments. It reduces eye strain and makes data layers like heatmaps and route lines visually pop against the background.

Step 4: Add Markers and Popups to Your Map

A map without markers is just a background image. Markers turn a static view into something users can interact with.

Single Marker with a Popup

const popup = new mapmetricsgl.Popup().setHTML(`
  <strong>Amsterdam Central</strong>
  <p>Stationsplein, 1012 AB Amsterdam</p>
`);

new mapmetricsgl.Marker({ color: '#97C70A' })
  .setLngLat([4.9001, 52.3791])
  .setPopup(popup)
  .addTo(map);

Click the marker and the popup opens. You can put any HTML inside: addresses, thumbnails, CTA buttons, whatever your UI calls for.

Plotting Multiple Markers from Data

Most real-world apps need more than one pin. Loop over an array and create a marker for each entry:

const locations = [
  { name: 'Amsterdam', coords: [4.9041, 52.3676] },
  { name: 'Rotterdam', coords: [4.4777, 51.9244] },
  { name: 'Utrecht',   coords: [5.1214, 52.0907] },
];

locations.forEach(({ name, coords }) => {
  const popup = new mapmetricsgl.Popup().setHTML(`<strong>${name}</strong>`);
  new mapmetricsgl.Marker({ color: '#97C70A' })
    .setLngLat(coords)
    .setPopup(popup)
    .addTo(map);
});

Performance note: Once you exceed roughly 100 to 200 markers, rendering slows noticeably on zoomed-out views. Enable GeoJSON source clustering (supported out of the box by the SDK) to group nearby markers at low zoom levels. Check the SDK docs for clustering configuration.

Step 5: Add Address Search with the Geocoding API

The Geocoding API turns a text query (a street address, city name, or landmark) into coordinates you can pan to, mark, or feed into a routing request.

async function searchAddress(query) {
  const url = new URL('https://api.mapatlas.eu/geocoding/v1/search');
  url.searchParams.set('text', query);
  url.searchParams.set('key', 'YOUR_API_KEY');

  const res  = await fetch(url);
  const data = await res.json();

  if (!data.features.length) return;

  const [lng, lat] = data.features[0].geometry.coordinates;
  const label      = data.features[0].properties.label;

  map.flyTo({ center: [lng, lat], zoom: 14 });

  new mapmetricsgl.Marker({ color: '#97C70A' })
    .setLngLat([lng, lat])
    .setPopup(new mapmetricsgl.Popup().setHTML(`<strong>${label}</strong>`))
    .addTo(map);
}

// Try it
searchAddress('Rijksmuseum, Amsterdam');

Results come back as GeoJSON features, so they plug directly into any GeoJSON-compatible layer, data table, or downstream API call.

Build a live search bar in under 30 lines: Attach searchAddress to the input event of a text field, debounce it by 300ms, and you've got autocomplete-style map search with no extra dependencies.

Integrating Interactive Maps with React

A Reusable Map Component

Wrap the map initialization in useEffect so it runs after the DOM mounts, and return a cleanup function to prevent memory leaks on unmount:

import { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

export function MapAtlasMap({
  apiKey,
  center = [4.9041, 52.3676],
  zoom   = 12,
}) {
  const containerRef = useRef(null);

  useEffect(() => {
    const map = new mapmetricsgl.Map({
      container: containerRef.current,
      style: `https://tiles.mapatlas.eu/styles/basic/style.json?key=${apiKey}`,
      center,
      zoom,
    });

    return () => map.remove();
  }, [apiKey]);

  return <div ref={containerRef} style={{ width: '100%', height: '500px' }} />;
}

Use it anywhere in your component tree:

<MapAtlasMap
  apiKey={process.env.NEXT_PUBLIC_MAPATLAS_KEY}
  center={[4.9041, 52.3676]}
  zoom={13}
/>

Handling Next.js Server-Side Rendering

The map SDK depends on browser APIs (window, document) that don't exist during SSR. Import the component dynamically with SSR disabled:

import dynamic from 'next/dynamic';

const MapAtlasMap = dynamic(
  () => import('./MapAtlasMap').then(m => m.MapAtlasMap),
  {
    ssr: false,
    loading: () => (
      <div style={{ height: 500, background: '#f0f1f3', borderRadius: 12 }} />
    ),
  }
);

The loading placeholder keeps your layout stable while the map bundle downloads, preventing cumulative layout shift (CLS), which matters for both user experience and Core Web Vitals.

Production Performance Checklist

Before shipping, run through these optimizations:

  • Lazy-load maps below the fold. Use IntersectionObserver to initialize the map only when its container scrolls into view. This defers ~200 KB of JavaScript from the initial page load.
  • Stick with vector tiles. Vector tiles scale cleanly to any screen density, load faster than raster images, and can be restyled client-side without additional server requests. MapAtlas serves vector tiles by default.
  • Cluster large marker sets. Beyond 100 to 200 markers, unclustered rendering on a zoomed-out view causes noticeable frame drops. Clustering solves this entirely.
  • Keep your API key server-side. Never commit keys to a public repository. Use environment variables (NEXT_PUBLIC_MAPATLAS_KEY in Next.js) or proxy requests through your backend for sensitive operations.
  • Set maxBounds for regional apps. If your users only care about one geography, restrict the viewport so tiles outside that area are never requested. Fewer network calls, faster load.

What to Build Next

You've got a map that renders, shows markers, searches addresses, and integrates with React. Here's where to go from here:

  • Routing API: Request turn-by-turn directions between two coordinates. Returns a route polyline, total distance, and estimated travel time.
  • Isochrone API: Generate a polygon covering every point reachable within n minutes. Used for delivery zones, service coverage maps, and catchment area analysis.
  • Matrix API: Calculate travel time and distance between multiple origins and destinations in a single request. Essential for fleet dispatch and logistics optimization.

Full SDK reference, style documentation, and API guides are available at docs.mapatlas.xyz.

Frequently Asked Questions

Can I add interactive maps to my website for free?

Yes. MapAtlas offers a free tier with no credit card required at sign-up. It includes vector tile rendering, the Geocoding API, and the Routing API. That's enough for development and small-scale production use.

How do I embed a map in a React or Next.js app?

Wrap the map initialization in a useEffect hook so it runs after the DOM mounts. In Next.js, use dynamic() with ssr: false to avoid server-side rendering errors. Both approaches are covered with copy-paste examples in this tutorial.

What are vector tiles, and why should I use them over raster?

Vector tiles describe map features (roads, buildings, labels) as mathematical geometry rather than pre-rendered pixel images. They scale sharply to any resolution, download faster, and can be restyled entirely on the client without additional server round-trips.

How many markers can I add before performance drops?

Rendering typically degrades beyond 100 to 200 markers at low zoom levels. The fix is clustering: the MapAtlas SDK supports GeoJSON source clustering natively, grouping nearby markers at low zoom and expanding them as the user zooms in.

Do I need GIS experience to use MapAtlas?

No. The SDK is designed for web developers, not GIS specialists. You initialize a map with coordinates and a zoom level, add markers with longitude/latitude pairs, and call the Geocoding API with plain text. No spatial databases or GIS tooling required.

Found this useful? Share it.

Back to Blog