When we developed the ShipBit website, we wanted to display a Google Map widget on our Contact page that should show our location and match the dark color scheme it is surrounded with. This was actually a bit tricky to implement in Svelte & Sapper without prior knowledge and experience in these technologies.

In general you can embed Google Maps via iFrame or their JavaScript API. While the iFrame version is definitely easier to use, it lacks some customization options and does not let you modify the color scheme apart from some rather hacky CSS solutions, so we went with the JS API.

First you need to create an API key and configure it according to the documentation. Once you have set this up, you can integrate the Google Maps API in your Svelte page:

<script>
  import Map from "../components/Map.svelte";

  let ready;

  if (process.browser) {
    window.initMap = () => {
      ready = true;
    };
  }
</script>

<svelte:head>
  <script defer async src="https://maps.googleapis.com/maps/api/js?key=YOUR-API-KEY&callback=initMap&libraries=places"></script>
</svelte:head>

<section>
  {#if ready}
    <Map />
  {/if}
</section>

We asynchronously load the required scripts from a CDN and also include the Places API which we’ll later use to find the correct start location of the map. Note that the script tag includes a callback initMap that will be triggered automatically once all required scripts are loaded. This is the tricky part because Google expects initMap to be a global handler registered on the window object. Sapper uses server-side rendering and has no access to any window object in the client. That’s why we check if the Svelte component is currently being executed on the server-side or in the client and only if it’s the latter, we register the callback handler. Once this is triggered, we know that all scripts are available and that we can finally render the map.

Now take the Styled Maps: Night Mode template as base for your Map.svelte component:

<script>
  import { onMount } from "svelte";

  let container;
  let map;
  let service;
  let infowindow;
  let zoom = 15;
  let center = { lat: 51.5127494, lng: 7.4829757 };

  onMount(async () => {
    const options = {
      zoom,
      center,
      styles: [
        // paste "Styled Maps: Night Mode" styles here
      ],
    };
    // setup the general Map with the provided options
    map = new google.maps.Map(container, options);

    // query the Google Places API
    const request = {
      query: "ShipBit",
      fields: ["name", "geometry"],
    };
    service = new google.maps.places.PlacesService(map);

    // create a custom marker at the found place
    service.findPlaceFromQuery(request, (results, status) => {
      if (status === google.maps.places.PlacesServiceStatus.OK) {
        for (let i = 0; i < results.length; i++) {
          const place = results[i];
          const marker = new google.maps.Marker({ map, position: place.geometry.location });

          marker.addListener("click", () => {
            infowindow = new google.maps.InfoWindow({
              content: `Add your marker HTML content here`,
            });
            infowindow.open(map, marker);
          });
        }
        map.setCenter(results[0].geometry.location);
      }
    });
  });
</script>

<style lang="scss">
  .map {
    position: relative;
    left: 0;
    width: 100%;
    height: 500px;
  }
    // this is how you can restyle certain classes in the map
  :global(.gm-style .gm-style-iw) {
    color: black;
    font-size: 1.125rem;
  }
</style>

<div class="map" bind:this={container} />

We first setup the general map with some input parameters like the initial zoom level and also integrate the dark scheme styles copied before. Once the map is configured, we call the Places API to search for our company and center it in the map. If your desired location is not registered as Google Place or you want to set the location manually, just skip this step and use the center attribute of the Map component instead. Once we located the place, we add a custom marker at its exact location that the user can click to see additional information. We also added some styles to make the map responsive and resize correctly on different viewports.

Caveats

When reloading the page, the map sometimes complains that the initMapcallback is not registered (yet?). It seems like Svelte (or Sapper) does something different regarding the lifecycle functions compared to navigating to the page initially or moving back and forth from another page. In our case, this is not a big issue because there is no reason for users to refresh the page and if they do, they’ll probably figure it out because they saw the map at least once. If you know why this is happening, please let us know!