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
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.
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="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
/>
</MapContainer>
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 © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">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 © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>'
/>
<Marker position={[40.8054, -74.0241]} draggable={true} animate={true}>
<Popup>Hey ! I live here</Popup>
</Marker>
</MapContainer>
);
};
export default Map;
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>
);
}
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 (10)
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
so that when the marker is moved, we will get the current position (lat,lng) of that marker
Thanks alot , you saved me
Fantastic post - really showing some useful insights - found it look for the windows SSR issue
wow, excelente tu publicacion me ayudo mucho a resolver el problema de ssr de windows!!!
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
Hi, I think the issue might be an incorrect import or the Map component being rendered directly instead of the MapWithNoSSR component.
Good One !!
<MapContainer style={{ height: "100%", width: "100%" }}>
was necessary to get it working for me.Thanks man, that was helpful
Thanks a lot