DEV Community

Cover image for How to use Mapbox in Next.js
saksham
saksham

Posted on • Updated on • Originally published at recodebrain.com

How to use Mapbox in Next.js

Table of content

  1. Mapbox
  2. Next.js
  3. Initializing the map
  4. Adding styles
  5. Adding the map controls
  6. Loading the data
  7. Adding the custom markers
  8. Adding the marker popup
  9. Conclusion

Mapbox

At the time of writing this article, to use Google Maps API, you need billing information (even though it is free for certain limits). So, Mapbox can be a better alternative to Google Maps which provides, 50,000 free map loads for the web (without billing credentials). It provides a range of APIs, SDKs, and developer tools for maps, navigation, and search across platforms.

Setting up the Mapbox key

  • Goto Mapbox and create an account or login if you have one.
  • In your account dashboard, under Access tokens section, select Create a token button.
  • In Create an access token page, name your token and hit Create token.
  • While creating token you can also add different types of token restrictions and url restriction. But for current app we will simply use the default settings.

Next.js

In this article, we are using Next.js 13 for our app. We opted for App Router for our app instead of Page router. Thus, the src/app directory will contain our root route example.com/. The page.js file will define a unique page for each route, and the layout.js file will define the UI shared between multiple pages like navigation, header, and footer. We can also nest layouts.

But for simplicity, we will use the root route only. Thus, our initial file structure looks like this:

  src
  ├─ app/
  │  ├─ globals.css
  │  ├─ layout.js
  │  ├─ page.js
  package-lock.json
  package.json
Enter fullscreen mode Exit fullscreen mode

Dependencies

In this app, we will use react-map-gl and react-icons. We will use the react-map-gl package to use Mapbox components in the Next.js app.

To install the dependencies run:

npm install react-map-gl react-icons --save

Adding key in Next.js environment

We will add the access token we created in the above section as an environment variable in our Next.js app. In your Next.js root folder, create a .env.local file (if you don't have one) and use the prefix NEXT_PUBLIC_ with the variable name.

# .env.local
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN=<Your_Mapbox_Access_Token>
Enter fullscreen mode Exit fullscreen mode

Initializing the map

In the page.js file, we will import the Map component from the react-map-gl package. The Map component will render the map. But first, we need to supply the following values to initialize the Map component:

  1. mapboxAccessToken: This attribute takes in the Mapbox access token that we had stored in the .env.local file. We can access this token using the process.env global variable.
  2. mapbox-gl.css: This file consists of all the styling for our Map component and its child elements. Failing to import this file will result in the inaccurate rendering of the Map component and its elements like Marker, Popup, Navigation Controls, etc.
  3. initialViewState: This attribute takes in the initial location (latitude, longitude, and zoom level). When the map first loads this location will be rendered.
// page.js
"use client";

import Map from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";

import classes from "./Page.module.css";

export default function Home() {
    const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;

    return (
        <main className={classes.mainStyle}>
            <Map
                mapboxAccessToken={mapboxToken}
                mapStyle="mapbox://styles/mapbox/streets-v12"
                style={classes.mapStyle}
                initialViewState={{ latitude: 35.668641, longitude: 139.750567, zoom: 10 }}
                maxZoom={20}
                minZoom={3}
            ></Map>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Adding styles

In src/app/globals.css add the following style:

/* globals.css */

body {
    font-family: Arial, Helvetica, sans-serif;
}
/* Overrides the original padding of the Popup component in the Map */
.mapboxgl-popup-content {
    padding: 0 !important;
}
Enter fullscreen mode Exit fullscreen mode

Create a src/app/Page.module.css file and add the style for map components:

/* Page.module.css */

.mainStyle {
    max-width: 100%;
    height: 100vh;
}
.mapStyle {
    width: 100%;
    height: 100%;
}
.popupTitle {
    background-color: #87bd41;
    font-weight: 700;
    font-size: medium;
    color: ivory;
    padding: 10px;
}
.popupInfo {
    font-weight: 400;
    font-size: 14px;
    padding: 10px;
}
.popupLabel {
    font-weight: bold;
}
.popupWebUrl {
    color: dodgerblue;
}
.popupWebUrl:active,
.popupWebUrl:focus {
    outline: 0;
}
Enter fullscreen mode Exit fullscreen mode

Adding the map controls

The map controls generally include Navigation controls and Geo-location controls. Under Navigation controls, we get zoom-in, zoom-out, and compass controls. Under Geo-location controls, we get user location control.

// page.js
"use client";

import Map, { NavigationControl, GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";

import classes from "./Page.module.css";

export default function Home() {
    const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;

    return (
        <main className={classes.mainStyle}>
            <Map
                mapboxAccessToken={mapboxToken}
                mapStyle="mapbox://styles/mapbox/streets-v12"
                style={classes.mapStyle}
                initialViewState={{ latitude: 35.668641, longitude: 139.750567, zoom: 10 }}
                maxZoom={20}
                minZoom={3}
            >
                <GeolocateControl position="top-left" />
                <NavigationControl position="top-left" />
            </Map>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Our initial map looks like this:

Initial Map

Loading the data

For illustration, we will use some airport data. It is not structured in standard geospatial formats like GeoJSON, KML, OSM etc. It is represented in simple JSON format, but it works for now. In the project root, create a dummyData folder with the airports.json file. We are using partial data on airports in Japan, which is available here. You can get complete data from here.

Import by adding import airports from "../../dummyData/airports.json"; in import section of the page.js file. Our project structure will look like this:

  dummyData
  ├─ airports.json
  src
  ├─ app/
  │  ├─ globals.css
  │  ├─ layout.js
  │  ├─ page.js
  │  ├─ Page.module.css
  package-lock.json
  package.json
Enter fullscreen mode Exit fullscreen mode

Adding the custom markers

We will use the Array.map() function to iterate through each airport's information from the airports.json file. In each iteration, we will return the Marker component from the react-map-gl package and each Marker will represent the airport data (point of interest) on the map.

To customize the map marker, we will provide icons from the react-icons package as button to the Marker component. The onClick() event on the button will set the selectedMarker state and add flyTo animation to the map. To animate the map, we will use the useRef React hook.

// page.js
"use client";
import { useState, useRef } from "react";

import Map, { Marker, Popup, NavigationControl, GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { IoMdAirplane } from "react-icons/io";

import airports from "../../dummyData/airports.json";
import classes from "./Page.module.css";

export default function Home() {
    const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;
    const [selectedMarker, setSelectedMarker] = useState(null);
    const mapRef = useRef(null);

    const zoomToSelectedLoc = (e, airport, index) => {
        // stop event bubble-up which triggers unnecessary events
        e.stopPropagation();
        setSelectedMarker({ airport, index });
        mapRef.current.flyTo({ center: [airport.lon, airport.lat], zoom: 10 });
    };

    return (
        <main className={classes.mainStyle}>
            <Map
                ref={mapRef}
                // attributes...
            >
                {/*Geolocate and Navigation controls...*/}

                {airports.map((airport, index) => {
                    return (
                        <Marker key={index} longitude={airport.lon} latitude={airport.lat}>
                            <button
                                type="button"
                                className="cursor-pointer"
                                onClick={(e) => zoomToSelectedLoc(e, airport, index)}
                            >
                                {<IoMdAirplane size={30} color="tomato" />}
                            </button>
                        </Marker>
                    );
                })}
            </Map>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Adding the marker popup

Marker popup can be useful to provide comprehensive information about the point of interest in our map. When a marker is selected, we will use React state to toggle the popup for that particular marker.

// page.js
"use client";
import { useState, useRef } from "react";

import Link from "next/link";

import Map, { Marker, Popup, NavigationControl, GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { IoMdAirplane } from "react-icons/io";

import airports from "../../dummyData/airports.json";
import classes from "./Page.module.css";
export default function Home() {
    // functions...

    return (
        <main className={classes.mainStyle}>
            <Map
            // attributes...
            >
                {/*Geolocate and Navigation controls...*/}
                {/*Airport Markers...*/}

                {selectedMarker ? (
                    <Popup
                        offset={25}
                        latitude={selectedMarker.airport.lat}
                        longitude={selectedMarker.airport.lon}
                        onClose={() => {
                            setSelectedMarker(null);
                        }}
                        closeButton={false}
                    >
                        <h3 className={classes.popupTitle}>{selectedMarker.airport.name}</h3>
                        <div className={classes.popupInfo}>
                            <label className={classes.popupLabel}>Code: </label>
                            <span>{selectedMarker.airport.code}</span>
                            <br />
                            <label className={classes.popupLabel}>Country: </label>
                            <span>{selectedMarker.airport.country}</span>
                            <br />
                            <label className={classes.popupLabel}>Website: </label>
                            <Link
                                href={selectedMarker.airport.url === "" ? "#" : selectedMarker.airport.url}
                                target={selectedMarker.airport.url === "" ? null : "_blank"}
                                className={classes.popupWebUrl}
                            >
                                {selectedMarker.airport.url === "" ? "Nil" : selectedMarker.airport.url}
                            </Link>
                        </div>
                    </Popup>
                ) : null}
            </Map>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Our final map looks like this:

Final Map

Conclusion

In this article, we created a map application in Next.js using the Mapbox API. We rendered the map and plotted the airport location with additional information using the popup.

You can add many features to the application and create fascinating map applications. We will explore these features in the upcoming articles. Until then, happy coding.

Thanks for reading this article. Don't forget to check out my other articles. You can support this article by sharing it. Bye 👋

Top comments (0)