DEV Community

Cover image for Integrating Next.js with Leaflet.js + Mapbox
Tushar saxena
Tushar saxena

Posted on • Edited on

Integrating Next.js with Leaflet.js + Mapbox

Do you want to include interactive maps in your Nextjs application?Then you must have come across Leafletjs. Though Leafletjs is very simple to use, but when it comes to Server Side rendered(SSR) applications build with Nextjs it lacks a little which can be annoying at times. But don't you worry I've found a workaround for this.

To set the scene let us first know 👇

Why Leafletjs?

Leaflet is the leading open-source JavaScript library for mobile-friendly interactive maps. Weighing just about 39 KB of JS, it has all the mapping features most developers ever need.

While Leaflet is meant to be as lightweight as possible, and focuses on a core set of features, an easy way to extend its functionality is to use third-party plugins. Thanks to the awesome community behind Leaflet, there are literally hundreds of nice plugins to choose from. We'll use one of those plugins in an example later in this post.

Tutorial

Please note that in this tutorial, I am making the assumption that you already have an existing Next.js project up and running. If you don’t, please start by traversing the Next.js documentation.

Install the required dependencies

npm i leaflet leaflet-defaulticon-compatibility leaflet-geosearch react-leaflet

Note: if you use TypeScript, make sure you install @types/leaflet otherwise you'll get compile errors on certain attributes used in the example.

I'll explain the requirement of each as we use them further in the tutorial.

Creating the map component

In your application create a map.jsx file inside the component folder ./component/Map.jsx.

It is important that this code is in a separate file from where it is embedded into your page because otherwise you'll get a window undefined error about which we'll talk later.

Side note: For Typescript users the file is called as map.tsx.
Inside the file put the following code

import { MapContainer, TileLayer,Marker,Popup } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'
import "leaflet-defaulticon-compatibility";

const Map = () => {
  return (
    <MapContainer center={[40.8054,-74.0241]} zoom={14} scrollWheelZoom={false} style={{height: "100%", width: "100%"}}>
      <Marker 
      position={[40.8054,-74.0241]}
      draggable={true}
      animate={true}
      >
        <Popup>
          Hey ! you found me
        </Popup>
      </Marker>
    </MapContainer>
  )
}

export default Map
Enter fullscreen mode Exit fullscreen mode

In the above example I have used some basic attributes from react-leaflet to initialize the map.

  • center: centers the map around the provided latitude & longitude.
  • zoom: Initial zoom for the Map ranging from 0 to 18.
  • scrollWheelZoom: yes it is exactly what it sounds like.
  • position: sets the position for the Marker.
  • draggable: helps drag and drop your marker on map.
  • animate: if true, panning will always be animated.

There are lot many features and examples available in react-leaflet documentation.

Setting up Mapbox API

In the above example, we will be using Mapbox API to add custom title layer to our map.
Mapbox plugin is quietly supported by leaflet and it also provides you with many custom mapping styles, you can even create your very own styles in their studio, for this part of the tutorial will use the default styles.

Alt Text

The first thing we’ll need to set up our custom Mapbox style is to have an account. I'm not going to walk you through that process, but you can head over to Mapbox’s website where you can sign up for free.

To generate a token that we’ll use for providing access to our Map.

  • Head on over to the Account section of the Mapbox dashboard which you can access by clicking over your profile in the top right section of the navbar.
  • Mapbox provides you with a “default” token that you can use in your applications. You're free to use this, but I recommend creating a new token that you can provide a unique name.

Configuring our custom endpoint
For this tutorial, we’re going to use Mapbox’s Static Tiles service. You can copy the endpoint from there which will look like this.

https://api.mapbox.com/styles/v1/{username}/{style_id}/tiles/256/{z}/{x}/{y}@2x?access_token={access_token}

There are a few parameters here we need to understand:

  • username: this will be your Mapbox account’s username
  • style_id: this will be the ID of the style you are using
  • z, x, y: these are parameters that Leaflet programmatically swaps out, so we want to leave them as is
  • access_token: this is the Mapbox key you created above

For this part of the example, we are using styles provided by Mapbox itself. You can also create your very own styles in Mapbox but for now, will use the streets-v11 from here.

And once I update my endpoint parameters, the final tilepoint URL will look like:
https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}@2x?access_token=MY_ACCESS_TOKEN

Since the style is provided by mapbox thus username in the URL is replaced with mapbox, if you are using your own style then you'll replace it with your own username.

Adding a custom TileLayer to React Leaflet

Inside of your <MapContainer> component in map.jsx you include a <TileLayer> component, which defines the imagery of the world that you base your map upon.

The example on the React Leaflet homepage uses a public version of OpenStreetMap as their TileLayer, which is an open source map project created and updated by people all around the world.

<MapContainer center={position} zoom={13}>
  <TileLayer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
  />
</MapContainer>
Enter fullscreen mode Exit fullscreen mode

This gives you a basic map, but we want to swap in Mapbox so we can set up a custom look and feel for our map.

To add our custom style, we’ll want to update the url and attribution props of the TileLayer component.

For URL, it will simply be the custom style endpoint we created earlier, so in my example, it looks like:

https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}@2x?access_token=MY_ACCESS_TOKEN

For attribution, we want to credit Mapbox as the service, so we want to set our attribution as:

Map data &copy; <a href=&quot;https://www.openstreetmap.org/&quot;>OpenStreetMap</a> contributors, <a href=&quot;https://creativecommons.org/licenses/by-sa/2.0/&quot;>CC-BY-SA</a>, Imagery &copy; <a href=&quot;https://www.mapbox.com/&quot;>Mapbox</a>

When plugged in to our TileLayer, our map.jsx should now look like this:

import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet-defaulticon-compatibility";

const Map = () => {
  return (
    <MapContainer
      center={[40.8054, -74.0241]}
      zoom={14}
      scrollWheelZoom={false}
      style={{ height: "100%", width: "100%" }}
    >
      <TileLayer
        url={`https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}@2x?access_token=MY_ACCESS_TOKEN`}
        attribution='Map data &copy; <a href=&quot;https://www.openstreetmap.org/&quot;>OpenStreetMap</a> contributors, <a href=&quot;https://creativecommons.org/licenses/by-sa/2.0/&quot;>CC-BY-SA</a>, Imagery &copy; <a href=&quot;https://www.mapbox.com/&quot;>Mapbox</a>'
      />
      <Marker position={[40.8054, -74.0241]} draggable={true} animate={true}>
        <Popup>Hey ! I live here</Popup>
      </Marker>
    </MapContainer>
  );
};

export default Map;
Enter fullscreen mode Exit fullscreen mode

Finally let's render our Map

As you might know, the global window object is not available in SSR, you'll get a ReferenceError if you try using it there.
Now to avoid this we can take advantage of Nextjs's dynamic loading which will help to prevent SSR.
Inside ./pages/index.js emmbed your Map component like this:

import React from "react";
import dynamic from "next/dynamic";

export default function Home() {
  const MapWithNoSSR = dynamic(() => import("../component/map"), {
    ssr: false
  });

  return (
    <main>
      <div id="map">
        <MapWithNoSSR />
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

And that's it you're good to go with something like this 👇

Closing thoughts

I hope this quick tutorial was helpful to you in some way 😊. I know it would have saved me quite a bit of work if I had this before I went down my Next.js + leafletjs path. Once you’ve got it all working, Don't forget to provide me with your valuable feedback. Good luck!👍

Top comments (11)

Collapse
 
isneverdead profile image
Fariz Akbar

just a little add here for others wondering how to capture the position(lat,lng) after the marker is dragged,

i just added a ref to the marker and eventHandlers function, below is the full code

import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'

const markerRef = useRef(null)
const eventHandlers = useMemo(
        () => ({
            dragend() {
                const marker = markerRef.current
                if (marker != null) {
                    // setPosition(marker.getLatLng())
                    console.log(marker.getLatLng())
                }
            },
        }),
        [],
    )
 useEffect(() => {
        console.log(markerRef.current)
    }, [markerRef.current])

//////////////////////
/// in the html

<MapContainer center={[40.8054, -74.0241]} zoom={14} scrollWheelZoom={false} style={{ height: "400px", width: "100%" }}>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
   <Marker
   eventHandlers={eventHandlers}
   position={[40.8054, -74.0241]}
   draggable={true}
   animate={true}
   ref={markerRef}
   >
       <Popup>
           Hey ! you found me
       </Popup>
   </Marker>
</MapContainer>
Enter fullscreen mode Exit fullscreen mode


so that when the marker is moved, we will get the current position (lat,lng) of that marker

Collapse
 
saman3230 profile image
Saman Soroushnia

Thanks alot , you saved me

Collapse
 
nielsgregers profile image
Niels Gregers Johansen

Fantastic post - really showing some useful insights - found it look for the windows SSR issue

Collapse
 
arevalodev profile image
Arevalo-dev

wow, excelente tu publicacion me ayudo mucho a resolver el problema de ssr de windows!!!

Collapse
 
tesshsu profile image
tess hsu

Hi I install but got ReferenceError: document is not defined

This error happened while generating the page. Any console logs will be displayed in the terminal window.
from file:///C:/Users/TESS/Documents/GitHub/ecoechange/front/node_modules/leaflet/dist/leaflet-src.js (1826:17)

I had tried lot method but still not fingure out, does it happend to you guys? and how to resolve it with react next js ? tks

Collapse
 
tintindas profile image
Upamanyu Das

Hi, I think the issue might be an incorrect import or the Map component being rendered directly instead of the MapWithNoSSR component.

Collapse
 
anshimishra02 profile image
anshimishra02

Good One !!

Collapse
 
vjpr profile image
Vaughan Rouesnel

<MapContainer style={{ height: "100%", width: "100%" }}> was necessary to get it working for me.

Collapse
 
leor37 profile image
Leandro Rocha

Thanks you!

Collapse
 
sabit990928 profile image
Sabit Rakhim

Thanks man, that was helpful

Collapse
 
pavanrajesh profile image
Pavan-Rajesh

Thanks a lot