4 gotchas when setting up Google Maps API with Next.js and ESLint
Tags: googlemaps, nextjs, eslint, react
Published: 2021-02-12 11:49:23

![Key lines of code in this article, written in calligraphic style](https://dev-to-uploads.s3.amazonaws.com/i/floor2vl7cf12rplabnf.png)
I've set up Google Maps JavaScript API for my Next.js app. The API documentation on how to get started ([Google 2021](https://developers.google.com/maps/documentation/javascript/overview)) is very well-written, but I encountered a few gotchas when the API is used together with Next.js and also ESLint. Let me take note of them below for your information (and for my future self).

**Updates on July 31, 2021**: A new section entitled **"Gotcha #4: API Key"** is added. Consequently, the title of this article is changed from "3 gotchas when setting up Google Maps API with Next.js and ESLint". 

**Updates on August 15, 2021**: A new section entitled **"Bonus: Remove all the default buttons"** is added at the end of the article.

**Updates on Sep. 3, 2021**: Change the link to Google's documentation on restricting URLs that can send API requests. Google Cloud's documentation is better written than Google Map Platform's on this matter.

## Gotcha #1: CSS

### TL;DR

Add the following CSS declaration:

```css
#__next {
  height: 100%;
}
```

### Detail

To show a Google Map across the entire browser window, [Google (2021)](https://developers.google.com/maps/documentation/javascript/overview) recommends the following CSS code:

```css
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}
#map {
  height: 100%;
}
```

where `#map` is the `id` for the container element in which a Google Map will be shown.

With Next.js, however, the `#map` container will not be a direct child of the `body` element. There will be another `div` with `#__next` as its `id` attribute. In other words, Next.js will compile your React code into the following HTML code:

```html
<html>
  <body>
    <div id="__next">
      <div id="map"></div>
    </div>
  </body>
</html>
```

By default, the `#__next` container has `height:auto`. As it doesn't recognize any content, the height will be zero. So the following CSS declaration

```css
#map {
  height: 100%;
}
```

 will set the height of the `#map` container to be 100% of zero. That is, zero. As a result, a Google Map inside the container won't be shown.

A workaround is suggested by [SkyzohKey (2018)](https://stackoverflow.com/a/53640085/11847654):

```css
#__next {
  height: 100%;
}
```

This will ensure that the `#__next` container's height will be 100% of the `body` element's height, which is in turn 100% of the `html` element's height, which is in turn 100% of the browser window height. 

- Incidentally, I haven't found any documentation saying the `height:100%` will refer to the browser window height when it's applied to the `html` element. Let me know if you know where to look for.

Consequently, the `#map` container's height will be 100% of the `#__next` container, that is, the browser window height.


## Gotcha #2: React hooks

### TL;DR

Compose the `pages/index.js` as follows:

```js
// pages/index.js

import {useEffect, useRef} from 'react';
import {Loader} from '@googlemaps/js-api-loader';

function HomePage() {
  const googlemap = useRef(null);

  useEffect(() => {
    const loader = new Loader({
      apiKey: 'yourAPIkey',
      version: 'weekly',
    });
    let map;
    loader.load().then(() => {
      map = new google.maps.Map(googlemap.current, {
        center: {lat: -34.397, lng: 150.644},
        zoom: 8,
      });
    });
  });
  return (
    <div id="map" ref={googlemap} />
  );
}

export default HomePage;
```

### Detail

[Google (2021)](https://developers.google.com/maps/documentation/javascript/overview) suggests the following JavaScript code to embed a Google Map:

```js
  map = new google.maps.Map(document.getElementById("map"), {
    center: { lat: -34.397, lng: 150.644 },
    zoom: 8,
  });
```

where the `#map` container is referenced with `document.getElementById("map")`. An experienced React user can immediately tell that this should be replaced with the `useRef` hook.

- For why we should use `useRef()` instead of `document.getElementById()`, see [Farmer (2018)](https://www.javascriptstuff.com/use-refs-not-ids/).

In addition, when we need to refer to the element during the initial rendering of a React component, we should use the `useEffect` hook. So all the JavaScript code to embed a Google Map needs to be written inside the `useEffect` hook block.

This is a technique I've learned for using the `canvas` element with React. See [Kudamatsu (2020)](https://masakudamatsu.medium.com/how-to-use-html-canvas-with-react-hooks-web-dev-survey-from-kyoto-e633812023b1) (see Step 4) for detail. 

## Gotcha #3: Handling ESLint error

### TL;DR

Add the following line immediately before creating a map instance:

```js
const google = window.google;
```

### Detail

The code in the previous two sections will do render a Google Map. But if you use ESLint, it throws an error because of this line:

```js
map = new google.maps.Map(googlemap.current, {...});
```

The object called `google` is used without being defined. ESLint doesn't like it. And it is a compile error. So you cannot tell ESLint to ignore this line of code ([ESLint 2019](https://eslint.org/docs/2.13.1/user-guide/configuring#disabling-rules-with-inline-comments)).

A workaround is suggested by [Abramov (2017)](https://stackoverflow.com/a/43718073/11847654). He explains why ESLint complains:

> "... people commonly misunderstand the difference between local variables, imported modules, and global variables, and so we want to always make it clear in the code when you use a global variable."

So to make it clear that `google` is a global variable, we should write the `useEffect ` block of code in the following way:

```js
  useEffect(() => {
    const loader = new Loader({
      apiKey: 'yourAPIkey',
      version: 'weekly',
    });
    let map;
    loader.load().then(() => {
      const google = window.google; // ADDED
      map = new google.maps.Map(googlemap.current, {
        center: {lat: -34.397, lng: 150.644},
        zoom: 8,
      });
    });
  });
```

The `window.google` is `undefined` until the Google API library is referenced ([Marcus 2018](https://stackoverflow.com/questions/50030311/window-google-is-undefined-in-react#comment87076715_50030311)). So it has to be inside the `loader.load().then()` block.

## Gotcha #4: API key

Embarrassingly, when I first used Google Maps API, I hard-coded its API key, committed it with Git, and pushed it to the GitHub repo. Google immediately emailed me with a message, urging me to change the API key as soon as possible.

Since then, I learned about how to secure API keys for a back-end server by saving them as environment variables defined in the `.env` file (which needs to be git-ignored) with the help of the `dotenv` library (see [Sanatan 2019](https://stackabuse.com/managing-environment-variables-in-node-js-with-dotenv) for detail).

This standard technique, however, cannot directly be applied to Google Maps API, which requires browsers, not back-end servers, to access to the API key. Plus, Next.js has its own complication when it comes to the use of environment variables.

I've figured out that there are two approaches to handle API keys when we use Google Maps API with Next.js. 

### Approach 1: Next.js built-in environment variables

Step 1: Create a file called `.env.local` in the root directory of a project. Next.js will automatically load environment variables in `.env.local` into `process.env`. For detail, see [Next.js documentation](https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables).

Step 2: Add your API key to the `.env.local` file in the following way:

```
NEXT_PUBLIC_API_KEY=ROCHjzuh5szlxhgjh2duYDHjdg
```

where a random series of characters to the right hand of `=` needs to be replaced with your own API key for Google Maps. The variable name to the left of `=` has to start with `NEXT_PUBLIC_`, followed by any name of your choice. Otherwise, browsers cannot access to its value. For detail, see [Next.js documentation](https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser). 

Step 3: Add `.env.local` to `.gitignore` so that your API key won't be committed to your Git repo.

Step 4: In the `useEffect` hook (see the "Gotcha #2: React hooks" section above), refer to the API key as `process.env.NEXT_PUBLIC_API_KEY`:

```js
  useEffect(() => {
    const loader = new Loader({
      apiKey: process.env.NEXT_PUBLIC_API_KEY,
      version: 'weekly',
    });

    let map; 
    loader.load().then(() => {
      ...
    })
 }) 
```

That's all!

But you may not like this  `NEXT_PUBLIC_` prefix. Also you may want to use `.env`, not `.env.local`, as the file name for environment variables. If so, there is an alternative approach.

### Approach 2: dotenv

This approach is a technique that I learned from [Surya (2021)](https://medium.com/courtly-intrepid/environmental-variables-in-next-js-with-dotenv-599c5bbfdf74). 

Step 1: Create a file called `.env`. (The file doesn't have to be in the root directory; see Step 5 below.)

Step 2: Add your API key to the `.env` file as follows:

```
API_KEY=ROCHjzuh5szlxhgjh2duYDHjdg
```

where a random series of characters to the right hand of `=` needs to be replaced with your own API key for Google Maps. Change `API_KEY` to some other name, if you wish. You don't have to prefix the variable name with `NEXT_PUBLIC_`. 

Step 3: Add `.env` to `.gitignore`.

Step 4: Install `dotenv` with

```bash
npm install dotenv
```

Step 5: Configure `dotenv` in `next.config.js` (the Next.js configuration file) as follows:

```js
const webpack = require('webpack');
const {parsed: myEnv} = require('dotenv').config();

module.exports = {
  webpack(config) {
    config.plugins.push(new webpack.EnvironmentPlugin(myEnv));
    return config;
  },
};
```

If you need to save `.env` somewhere else than the root directory of your project, say, `/src/.env`,  then change the second line to:

```js
const {parsed: myEnv} = require('dotenv').config({
    path:'/src/.env'
});
```

Step 6: In the `useEffect` hook, refer to the API key as `process.env.API_KEY`:

```js
  useEffect(() => {
    const loader = new Loader({
      apiKey: process.env.API_KEY,
      version: 'weekly',
    });

    let map; 
    loader.load().then(() => {
      ...
 })
```

That's it.

This approach requires an extra package and an extra configuration. In return, you gain more freedom in the naming of the environment variable for your API key and where to save the `.env` file.

### Security measures

Either of the above two approaches will expose your API keys in the Network tab of Chrome DevTools. As far as I understand, this is unavoidable because Google Maps API won't allow a map to be rendered with a server. Browsers need to make a request to Google Maps's server with your API key as part of a query string.

So Google Maps API documentation recommends restricting the URLs from which a request to Google Maps's server is made with your API key. If someone steals your API key, then, they won't be able to use it from their own web app.

For how to restrict URLs, see [Google Cloud documentation on using API keys](https://cloud.google.com/docs/authentication/api-keys#adding_http_restrictions).

## Summary

Your `pages/index.js` should look like this:

```js
// pages/index.js

import {useEffect, useRef} from 'react';
import {Loader} from '@googlemaps/js-api-loader';

function HomePage() {
  const googlemap = useRef(null);

  useEffect(() => {
    const loader = new Loader({
      apiKey: process.env.NEXT_PUBLIC_API_KEY,
      version: 'weekly',
    });

    let map; 
    loader.load().then(() => {
      const google = window.google;
      map = new google.maps.Map(googlemap.current, {
        center: {lat: -34.397, lng: 150.644},
        zoom: 8,
      });
    });
  });

  return (
    <div id="map" ref={googlemap} />
  );
}

export default HomePage;
```

Then add the following CSS declarations:

```css
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}

#__next {
  height: 100%;
}

#map {
  height: 100%;
}
```

Finally, save your API key as `NEXT_PUBLIC_API_KEY` in the `.env.local` file (which needs to be git-ignored) in the root directory. (See the subsection entitled "Approach 2" above if you want to avoid prefixing the environment variable for your API key with `NEXT_PUBLIC_` and/or to save it in `.env`.)

## Bonus: Remove all the default buttons

This is not "gotcha" due to the use of Next.js. But it's likely to be what interests those who have read this article this far.

By default, Google Maps will show several buttons: on the top left, there are the tabs to switch the map style to the satellite image; on the top right, the button to enter the full-screen mode; on the bottom right, the "pegman" to enter the street view mode and the zoom in/out buttons.
![How Google Maps API renders a map with the default settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pcyvgu5o34bztl0j2a3x.png)**How Google Maps API renders a map with the default settings** (image source: a screenshot taken by the author)

If you don't need them all, edit the `map` variable as follows:
```js
      map = new google.maps.Map(googlemap.current, {
        center: {lat: -34.397, lng: 150.644},
        zoom: 8,
        fullscreenControl: false, // remove the top-right button
        mapTypeControl: false, // remove the top-left buttons
        streetViewControl: false, // remove the pegman
        zoomControl: false, // remove the bottom-right buttons
      });
```
For the documentation on these and other options, see the ["Map Options interface" section of Google Maps JavaScript API V3 Reference](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions).

---

Hope this article helps you kick-start the use of Google Maps API in your Next.js project!

And maybe you're also interested in the following articles of mine about more advanced uses of Google Maps API:

{% link https://dev.to/masakudamatsu/day-4-customizing-google-maps-color-scheme-and-its-place-label-visibility-pia %}

{% link https://dev.to/masakudamatsu/day-6-customizing-place-markers-in-embedded-google-maps-how-to-render-them-as-yellow-asterisks-4c74 %}

{% link https://dev.to/masakudamatsu/day-12-showing-user-location-on-embedded-google-maps-with-geolocation-api-and-react-128j %}

## References

Dan Abramov (2017) “[An answer to ‘google is not defined in react app using create-react-app’](https://stackoverflow.com/a/43718073/11847654)”, *Stack Overflow*, May 1, 2017.

ESLint (2019) “[Disabling Rules with Inline Comments](https://eslint.org/docs/2.13.1/user-guide/configuring#disabling-rules-with-inline-comments)”, *ESLint User Guide*, Dec. 1, 2019.

Farmer, Andrew H. (2018) “[Why to use refs instead of IDs](https://www.javascriptstuff.com/use-refs-not-ids/)”, *JavaScript Stuff*, Jan 27, 2018.

Google (2021) “[Overview] (https://developers.google.com/maps/documentation/javascript/overview)”, *Maps JavaScript API Guides*, Feb. 10, 2021. 

Kudamatsu, Masa (2020) “[How to use HTML Canvas with React Hooks — Web Dev Survey from Kyoto](https://masakudamatsu.medium.com/how-to-use-html-canvas-with-react-hooks-web-dev-survey-from-kyoto-e633812023b1)”, *medium.com*, Dec. 9, 2020.

Marcus, Scott (2018) “[A comment to ‘window.google is undefined in react?’](https://stackoverflow.com/questions/50030311/window-google-is-undefined-in-react#comment87076715_50030311)”, *Stack Overflow*, Apr. 25, 2018.

Sanatan, Marcus (2019) "[Managing Environment Variables in Node.js with dotenv](https://stackabuse.com/managing-environment-variables-in-node-js-with-dotenv)", _Stack Abuse_, last updated on May 15, 2019.

SkyzohKey (2018) “[An answer to ‘Nextjs：How to change css of root div __next on specific page?’](https://stackoverflow.com/a/53640085/11847654)”, *Stack Overflow*, Dec. 5, 2018. 

Surya, Deepak (2021) "[Environmental Variables in Next.js with dotenv](https://medium.com/courtly-intrepid/environmental-variables-in-next-js-with-dotenv-599c5bbfdf74)", _Courtly & Intrepid_, Feb. 3, 2021.