Integrating MapBox with Next.js — the Cheaper Alternative to Google Maps
Tags: mapbox, nextjs, react, javascript
Published: 2020-12-05 08:18:49

---
title: Integrating MapBox with Next.js — the Cheaper Alternative to Google Maps
published: true
description: Working with MapBox’s Search API and Map API to build an interactive map with some clickable pins
tags: mapbox, nextjs, react, javascript
cover_image: https://images.unsplash.com/photo-1560532806-dbb46d88b494?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=3300&q=80
---

Lately, I’ve been working on creating an [online platform for eco hotels and resorts](sustaynably.com), and found myself needing to render a map with some clickable marker pins (which we are going to reproduce in this tutorial). After scouring the internet with possible solutions, two immediate options sprung up — Google Maps and MapBox. While most of us are familiar with Google Maps because of the overwhelming presence it has in all our lives, integrating it in an application, I found, is less than ideal. Building a bootstrapped project, I wanted to keep the costs at a minimum and Google Map’s [pricing structure](https://cloud.google.com/maps-platform/pricing) would mean that the costs would begin to add up.

## Enter MapBox!
With a competitive [pricing structure](https://www.mapbox.com/pricing/) (the first 50,000 requests on web are free) and an easy-to-use and well documented API, MapBox is a good alternative to Google Maps. It is also built on top of [OpenStreetMap](https://www.openstreetmap.org/), which is an open source mapping project. Win, win!

## What are we building?
We’re going to be querying MapBox’s search api to get some locations of an infamous coffee shop called Greggs, focusing our search on the Greater London region. Then, we are going to render these places in our MapBox `<Map>` component with a bunch of clickable markers. On click, these markers will display some dismissible popups.

The finished product will look something like,
![map](https://dev-to-uploads.s3.amazonaws.com/i/8ihpmagd55bqx9jmevyw.png)

# Let's Code!

## Make a MapBox account to get your access token
The first thing you will need to do is to make a MapBox account so that you can get an access token. We will use this token to make requests to the various MapBox APIs.
Once you have your access token, it is time to set up your very own Next.js project and integrate all the juicy functionality that MapBox provides.

## Setup a new Next.js project (Skip this if you already have a project of your own)

Setting up a Next.js project is straightforward, you can either follow the instructions laid out in the [official documentation](https://nextjs.org/learn/basics/create-nextjs-app) or run the following command to set up a new Next.js project (Make sure you have Node.js installed).
 
```
npx create-next-app mapbox-project
```

Then, `cd` into the `mapbox-project` directory and run the development server by running `npm run dev` or `yarn dev` . Et Voila! Your Next.js project is up and running!

## Setup A MapBox Map
Next up, it’s time to render a MapBox map in our project. We do this by adding a [MapBox library](https://github.com/visgl/react-map-gl) written by the team at Uber called react-map-gl. This contains a suite of React components for MapBox. Add this library to your project by running:

```
yarn add react-mapbox-gl
```

With this, we’re going to create a Map component which will live in `components/Map.js`.

Create your Map.js file and add the following code to it:

```
import { useState } from "react";
import ReactMapGL from "react-map-gl";
export default function Map() {
  const [viewport, setViewport] = useState({
  width: "100%",
  height: "100%",
  // The latitude and longitude of the center of London
  latitude: 51.5074,
  longitude: -0.1278,
  zoom: 10
});
return <ReactMapGL
  mapStyle="mapbox://styles/mapbox/streets-v11"
  mapboxApiAccessToken={process.env.MAPBOX_KEY}
  {...viewport}
  onViewportChange={(nextViewport) => setViewport(nextViewport)}
  >
</ReactMapGL>
}
```

This is not going to work just yet. One of the biggest features of Next.js is the server side rendering it offers. MapBox, however, requires the global window object in order to work correctly. If you are server side rendering your app, you will need to dynamically import it into your page. This means that instead of importing it like a regular component,

```
import Map from '../components/Map'
```

We will have to import it dynamically. We will do this by using Next.js `dynamic imports`.

In your `pages/index.js` file (or wherever you’re rendering your Map component) add the following code.

```
const Map = dynamic(() => import("../components/Map"), {
  loading: () => "Loading...",
  ssr: false
});
```
This means that our MapBox component will now selectively be rendered client side. Perfect!

The only thing we need to do now is to add MapBox’s CSS files to our project. The easiest way to do this is to modify your existing `_app.js` or by adding a custom `_document.js` file. Then add a link to the CSS to the `<Head>` in your render function. You can get the latest version of the CSS files in their [API documentation](https://docs.mapbox.com/mapbox-gl-js/api/).

```
<head>
<link href='https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css' rel='stylesheet' />
</head>
```

Perfect. Your Map should now be up and running! Let’s take this a step further and try rendering some clickable pins on our map.

## Use MapBox’s search API to fetch a list of landmarks

MapBox has a really handy geocoding API which you can be used to fetch a list of locations, with their latitudes and longitudes. We’re going to be fetching a list of Greggs (a take-away fast food and coffee shop) in London and render them as pins on our Map.
First, let’s query our list by adding a simple fetch call to the Mapbox geocoding API. We want to search within the geographic bounds of London and want to cap our search at 10 results (London is huge and Londoner’s love their Gregg’s vegan sausage rolls. We don’t want to overwhelm ourselves with all the possibilities!). MapBox’s Geocoding Place Search API takes the following parameters, with some additional query strings.

```
/geocoding/v5/mapbox.places/{search_text}.json
```
We will be using the limit query parameter to cap our results at 10, and the bbox parameter to specify the latitudinal and longitudinal bounds of London.
With all this in mind, our search url will look something like this:

```
https://api.mapbox.com/geocoding/v5/mapbox.places/greggs.json?access_token=${process.env.MAPBOX_KEY}&bbox=-0.227654%2C51.464102%2C0.060737%2C51.553421&limit=10

```

We can use this url, to make a simple fetch call in our page. Our modified page will now look something like,

```
const Map = dynamic(() => import("../components/Map"), {
  loading: () => "Loading...",
  ssr: false
});
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/greggs.json?access_token=${process.env.MAPBOX_KEY}&bbox=-0.227654%2C51.464102%2C0.060737%2C51.553421&limit=10`;
export default function IndexPage() {
  const [locations, setLocations] = useState([]);
  useEffect(() => {
    const fetchLocations = async () => {
      await fetch(url).then((response) =>
        response.text()).then((res) => JSON.parse(res))
      .then((json) => {
        setLocations(json.features);
      }).catch((err) => console.log({ err }));
    };
    fetchLocations();
  }, []);
  return (<Container>
    <Map />
  </Container>);
}
```

We now have a list of 10 Greggs locations!

## Using our search results to render pins on our map

Now that we have a list of places, we can render these on a Map. `react-map-gl` comes with a handy `<Marker>` component that makes our task pretty straight forward. First we need to pass these locations to our `<Map>` component.

```
return (<Container>
    <Map locations={locations} />
  </Container>);
```

Now, within out Map component, we need to render a pin for each of these locations by passing their latitude and longitude to the `<Marker>` component.

Our final Map component will look something like,

```
import { useState } from "react";
import ReactMapGL, { Marker } from "react-map-gl";
export default function Map({ locations }) {
  const [viewport, setViewport] = useState({
    width: "100%",
    height: "100%",
    // The latitude and longitude of the center of London
    latitude: 51.5074,
    longitude: -0.1278,
    zoom: 10
  });
return <ReactMapGL
  mapStyle="mapbox://styles/mapbox/streets-v11"
  mapboxApiAccessToken={process.env.MAPBOX_KEY}
  {...viewport}
  onViewportChange={(nextViewport) => setViewport(nextViewport)}
  >
  {locations.map((location) => (
    <div key={location.id}>
      <Marker
      latitude={location.center[1]}
      longitude={location.center[0]}
      offsetLeft={-20}
      offsetTop={-10}>
        <span role="img" aria-label="push-pin">📌</span>
      </Marker>
    </div>
  ))}
</ReactMapGL>
}
```

## Making the pins clickable

We’re almost there! The last thing we want to do to make these maps fully functioning and interactive, is to add a popup with the name of the place. Again, Mapbox comes with a handy Popup component that makes this easy to do. We will simply add an onClick handler to our pins which will capture the details of the selected location, then we will pass the latitude and the longitude of the selected location to our `<Popup>` component. It’ll all be clear in a second!
Within the Map component, add a `useState` hook to capture the selected location.

```
export default function Map({ locations }) {
  // UseState hook
  const [selectedLocation, setSelectedLocation] = useState({})
  const [viewport, setViewport] = useState({
    width: "100%",
    height: "100%",
    // The latitude and longitude of the center of London
    latitude: 51.5074,
    longitude: -0.1278,
    zoom: 10
  });
......
```
We will also modify the render block to add an onClick handler and the `<Popup>` component that we just mentioned.

```
......
return <ReactMapGL
  mapStyle="mapbox://styles/mapbox/streets-v11"
  mapboxApiAccessToken={process.env.MAPBOX_KEY}
  {...viewport}
  onViewportChange={(nextViewport) => setViewport(nextViewport)}
  >
  {locations.map((location) => (
    <div key={location.id}>
      <Marker
      latitude={location.center[1]}
      longitude={location.center[0]}
      offsetLeft={-20}
      offsetTop={-10}>
        <a onClick={() => {
          setSelectedLocation(location);
        }}>
          <span role="img" aria-label="push-pin">📌</span>
        </a>
      </Marker>
      {selectLocation.id === location.id ? (
      <Popup
      onClose={() => setSelectedLocation({})}
      closeOnClick={true}
      latitude={location.center[1]}
      longitude={location.center[0]}>
        {location.place_name}
      </Popup>) : (false)}
    </div>
  ))}
</ReactMapGL>
}
```

The `<Popup>` component takes an onClose handler which sets the selectedLocation to {}.

And that is all! We’ve managed to render a map, rendered some pins on our map and managed to make them clickable with popups! Here’s our final result:

![map](https://dev-to-uploads.s3.amazonaws.com/i/lk5uzokv8h1bm1s7x79j.png)