DEV Community

Masa Kudamatsu
Masa Kudamatsu

Posted on • Updated on • Originally published at masakudamatsu.Medium

4 gotchas when setting up Google Maps API with Next.js and ESLint

Key lines of code in this article, written in calligraphic style
I've set up Google Maps JavaScript API for my Next.js app. The API documentation on how to get started (Google 2021) 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:

#__next {
  height: 100%;
}
Enter fullscreen mode Exit fullscreen mode

Detail

To show a Google Map across the entire browser window, Google (2021) recommends the following CSS code:

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}
#map {
  height: 100%;
}
Enter fullscreen mode Exit fullscreen mode

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>
  <body>
    <div id="__next">
      <div id="map"></div>
    </div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

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

#map {
  height: 100%;
}
Enter fullscreen mode Exit fullscreen mode

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):

#__next {
  height: 100%;
}
Enter fullscreen mode Exit fullscreen mode

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:

// 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;
Enter fullscreen mode Exit fullscreen mode

Detail

Google (2021) suggests the following JavaScript code to embed a Google Map:

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

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).

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) (see Step 4) for detail.

Gotcha #3: Handling ESLint error

TL;DR

Add the following line immediately before creating a map instance:

const google = window.google;
Enter fullscreen mode Exit fullscreen mode

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:

map = new google.maps.Map(googlemap.current, {...});
Enter fullscreen mode Exit fullscreen mode

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).

A workaround is suggested by Abramov (2017). 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:

  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,
      });
    });
  });
Enter fullscreen mode Exit fullscreen mode

The window.google is undefined until the Google API library is referenced (Marcus 2018). 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 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.

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

NEXT_PUBLIC_API_KEY=ROCHjzuh5szlxhgjh2duYDHjdg
Enter fullscreen mode Exit fullscreen mode

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.

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:

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

    let map; 
    loader.load().then(() => {
      ...
    })
 }) 
Enter fullscreen mode Exit fullscreen mode

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).

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
Enter fullscreen mode Exit fullscreen mode

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

npm install dotenv
Enter fullscreen mode Exit fullscreen mode

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

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

module.exports = {
  webpack(config) {
    config.plugins.push(new webpack.EnvironmentPlugin(myEnv));
    return config;
  },
};
Enter fullscreen mode Exit fullscreen mode

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

const {parsed: myEnv} = require('dotenv').config({
    path:'/src/.env'
});
Enter fullscreen mode Exit fullscreen mode

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

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

    let map; 
    loader.load().then(() => {
      ...
 })
Enter fullscreen mode Exit fullscreen mode

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.

Summary

Your pages/index.js should look like this:

// 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;
Enter fullscreen mode Exit fullscreen mode

Then add the following CSS declarations:

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

#__next {
  height: 100%;
}

#map {
  height: 100%;
}
Enter fullscreen mode Exit fullscreen mode

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 settingsHow 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:

      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
      });
Enter fullscreen mode Exit fullscreen mode

For the documentation on these and other options, see the "Map Options interface" section of Google Maps JavaScript API V3 Reference.


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:

References

Dan Abramov (2017) “An answer to ‘google is not defined in react app using create-react-app’”, Stack Overflow, May 1, 2017.

ESLint (2019) “Disabling Rules with Inline Comments”, ESLint User Guide, Dec. 1, 2019.

Farmer, Andrew H. (2018) “Why to use refs instead of IDs”, JavaScript Stuff, Jan 27, 2018.

Google (2021) “Overview”, Maps JavaScript API Guides, Feb. 10, 2021.

Kudamatsu, Masa (2020) “How to use HTML Canvas with React Hooks — Web Dev Survey from Kyoto”, medium.com, Dec. 9, 2020.

Marcus, Scott (2018) “A comment to ‘window.google is undefined in react?’”, Stack Overflow, Apr. 25, 2018.

Sanatan, Marcus (2019) "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?’”, Stack Overflow, Dec. 5, 2018.

Surya, Deepak (2021) "Environmental Variables in Next.js with dotenv", Courtly & Intrepid, Feb. 3, 2021.

Discussion (3)

Collapse
rcabre95 profile image
Raphael Cabrera

Hey Masa,

This is a great article. There's not nearly enough Nextjs content out there as there should be. I guess that's because the technology is so new.
I'm pretty new to this myself, and I was wondering if you might be able to post your github repo to a google maps/nextjs project, so that I can get some context for these code snippets. Is that possible? Thanks in advance either way!

Collapse
asross311 profile image
Andrew Ross • Edited on

I am a bit confused by this particular snippet (referencing _document.js I'm assuming)

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

What about the <Main /> and <NextScript /> next/document components wrapped within <body></body>?

dev-to-uploads.s3.amazonaws.com/up...

Collapse
masakudamatsu profile image
Masa Kudamatsu Author

Thank you, Andrew, for liking my article (and following me!).

I wasn't articulate enough about this snippet. It's the HTML code generated by Next.js, not the JS code to be compiled by Next.js. I've revised the sentence that introduces this snippet in the article.

Hope it's now clear to you.