Next.jsプロジェクトでReact Leafletを使って地図を表示してみた

2022.02.07

こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。

最近Next.jsを触っているのですが、ふと「地図も表示してみたいな」と思ったので試してみることにしました。

地図表示が出来るライブラリとしては様々なライブラリがあると思いますが、React Leafletというライブラリが利用できそうなので今回はこちらを利用して試してみたいと思います。

React Leafletについて

有名なオープンソースの地図表示ライブラリとしてLeafletというライブラリがあります。

このライブラリはJavaScriptライブラリとなっており、Reactプロジェクトでもこのライブラリを直接読み込んで利用することもできるかと思います。一方でReact向けのReact Leafletというラッパーライブラリが存在します。

こちらのReact Leafletを利用するとReactプロジェクトでも地図表示がしやすそうだったので、こちらを利用して地図表示をしてみたいと思います。

やってみる

ではまずはNext.jsのプロジェクト作成からやっていきます。

Next.js プロジェクトの作成

create next-appを利用して作成していきます。プロジェクト名はhello-react-leafletにしました。

% yarn create next-app --typescript   
yarn create v1.22.17
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Installed "create-next-app@12.0.10" with binaries:
      - create-next-app
✔ What is your project named? … hello-react-leaflet
(...snip...)
We suggest that you begin by typing:

  cd hello-react-leaflet
  yarn dev

✨  Done in 45.28s.

Leaflet関連パッケージのインストール

以下のドキュメントを参考に必要なパッケージをインストールしていきます。

今回はNext.jsを利用しており、TypeScriptを利用する設定にしたので最終的に以下のコマンドでインストールをします。

% yarn add leaflet react-leaflet
% yarn add -D @types/leaflet

なお、ドキュメントには以下のように事前準備としてLeaflet(JavaScript版)のクイックスタートガイドに沿って事前準備をするように記載があるのですが、実際には特に実施することはありませんでした。

Before using React Leaflet, you must setup your project following Leaflet's Quick Start Guide.

Mapコンポーネントの作成とページの作成

インストールが済んだので、実際に地図を表示するためのコンポーネントを作成していきます。

Stack Overflowにとても参考になる回答があったので、こちらを参考に作成してみます。

まずはコンポーネントとして、map.tsxを作成します。上記のStack Overflowの回答にもありますが、コンポーネントとして作成しないとwindowの未定義エラー window is not defined が発生するので、コンポーネントとして作成してページから参照するようにします。

components/map.tsx

import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";

const Map = () => {
  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      scrollWheelZoom={false}
      style={{ height: "100vh", width: "100%" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[51.505, -0.09]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
};

export default Map;

コンポーネントでは、地図を全画面表示して画面の中央にマーカーも表示してあります。

更に、このコンポーネントを利用するページも作成していきます。

pages/map-page.tsx

import dynamic from "next/dynamic";
import React from "react";

function MapPage() {
  const Map = React.useMemo(
    () =>
      dynamic(() => import("../components/map"), {
        loading: () => <p>A map is loading</p>,
        ssr: false,
      }),
    []
  );
  return <Map />;
}

export default MapPage;

このコードでは、コンポーネントの再描画のチラつきを抑えるためにReact.useMemohookを利用しています。また、dynamicを利用して動的にコンポーネントをインポートし、更にサーバーサイドレンダリングをしない(ssr: false)ようにすることで、クライアントサイドに依存するコンポーネントを利用できるようにしています。

dynamicを利用したコードは最初はよく理解できなかったのですが、以下のエントリを参考にさせていただくことで意味が理解できました。

ドキュメントにもサーバーサイドレンダリングには対応していない旨の注意書きがされていますね。

Leaflet makes direct calls to the DOM when it is loaded, therefore React Leaflet is not compatible with server-side rendering.

地図を表示してみる

では、実際に動かして表示してみます。

% yarn dev
yarn run v1.22.17
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000

起動後に、http://localhost:3000にアクセスするとサンプルアプリの画面が表示されます。

今回は新しく作成した地図表示用のページを確認したいのでhttp://localhost:3000/map-pageにアクセスします。

表示されました!

が、一見良さそうに見えますがマーカー表示が壊れていますね。本来であれば画面中央にマーカーを表示したいです。

壊れたマーカー表示を直す

GitHubのIssueにずばり該当するものがあったので、こちらでコメントされていた方法で対応してみます。

components/map.tsx

import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";

import L from "leaflet";
import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerShadow from "leaflet/dist/images/marker-shadow.png";

delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
  iconUrl: markerIcon.src,
  iconRetinaUrl: markerIcon2x.src,
  shadowUrl: markerShadow.src,
});

const Map = () => {
  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      scrollWheelZoom={false}
      style={{ height: "100vh", width: "100%" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[51.505, -0.09]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
};

export default Map;

map.tsxにハイライト部分のコードを追加して、マーカーアイコンのURLを上書き修正しています。

今度はうまくいきました!想定どおりマーカーが表示されましたね。

まとめ

以上、Next.jsプロジェクトでReact Leafletを使って地図を表示してみました。

いくつかNext.jsならではの対応がありましたが、そこだけ対応できればReact Leafletを利用して地図表示ができました。あとはLeafletに慣れていれば問題なく地図表示ができそうですね。

どなたかのお役に立てば幸いです。それでは!