ゼスト Tech Blog

ゼストは「護りたい。その想いを護る。」をミッションに、在宅医療・介護業界向けのSaaSを開発しています。

@vis.gl/react-google-maps で実装する地図のコスト削減とUXの工夫

こんにちは、株式会社ゼストでエンジニアをしている永井です。

皆さん、マップ使ってますか?初めての場所に向かうとき、待ち合わせ場所を確認するときなど、日常的に使っている方が多いのではないでしょうか。

私たちが開発している ZEST SCHEDULE でも、住所設定や訪問予定の可視化など、さまざまな場面で Google Maps を活用しています。そこで今回は React × Google Maps の実装について、実践的なノウハウを紹介します。

React で Google Maps を使うには

ライブラリ選定

React で Google Maps を扱う場合、いくつかのライブラリの選択肢があります。

私たちは @vis.gl/react-google-maps を採用しました。このライブラリは vis.gl コミュニティ(OpenJS Foundation傘下)が開発・保守していますが、Google がスポンサーもしており、公式ドキュメントでも推奨されているライブラリです。

以前は Google 公式の @googlemaps/react-wrapper がありましたが、現在はアーカイブ(非推奨)となっており、公式リポジトリでも @vis.gl/react-google-maps への移行が案内されています。

@vis.gl/react-google-maps の特徴

  • TypeScript 対応
  • 活発にメンテナンスされている(2025年12月時点で v1.7.1)
  • useMap, useMapsLibrary など便利なフックが充実
  • Map, AdvancedMarker, InfoWindow などの React コンポーネントを提供

実装時に気になるポイント

Google Maps API で開発するにあたって、気になる点がいくつかあると思います。

  • コスト面: 何に対して課金されるのか?(頻繁なアクセスでコストが膨らまないか)
  • カスタマイズ性: Google Maps 上に複雑なカスタムピンなどを表示できるのか?

これらを調査した結果、どちらも問題ありませんでした。

  • 課金対象は地図の読み込み時(初期化)のみ
  • カスタムしたピンや経路を描画できる

※課金対象: 地図を表示してマーカーや線を描画するのみで、ルート検索や Places API など別途 API を呼ぶ場合は追加で費用がかかります。

実装 Tips

ここからは、私たちが実際に実装する中で工夫したポイントを紹介します。

1. reuseMaps でコスト対策

課金対象が地図の読み込みだと分かったので、コスト対策として地図の読み込み回数を減らしました。

例えば、地図をサイドメニューやモーダルに配置していると開閉のたびに読み込みが発生してコストが膨れてしまう可能性があります。

そこで活用したのが @vis.gl/react-google-mapsreuseMaps オプションです。 reuseMaps を有効にすることで、アンマウント時に Map インスタンスが破棄されずにキャッシュされ、再マウント時に再利用されます。

import { APIProvider, Map } from "@vis.gl/react-google-maps";

<APIProvider apiKey={API_KEY}>
  <Map
    mapId="YOUR_MAP_ID"
    reuseMaps
    ...
  >
    ...
  </Map>
</APIProvider>;

内部的な仕組みとしては、グローバル変数に Map インスタンスを保持しているだけでした。

お手軽な実装ですが、コスト面では結構効いてきます。

2. AdvancedMarker でカスタムピン

カスタムピンの実装には Google Maps APIAdvancedMarkerElement を使いました。

従来の Marker クラスでは画像や SVG を指定するだけで、柔軟なカスタム表示ができなかったようですが、AdvancedMarkerElement では HTML/React コンポーネントをそのままマーカーとして扱えるようになっています。

@vis.gl/react-google-maps では AdvancedMarker コンポーネントとして提供されています。

import { AdvancedMarker } from "@vis.gl/react-google-maps";

<AdvancedMarker position={{ lat: 35.6812, lng: 139.7671 }}>
  <CustomPin />
</AdvancedMarker>;

任意の React コンポーネントをマーカーとして表示できるので、デザインの自由度が高いです。

3. Polyline で経路表示

地点同士を線で繋ぐには Google Maps APIPolyline を使いました。Polyline を使うと、複数の座標を結ぶ線(直線)を地図上に描画できます。

@vis.gl/react-google-maps では Polyline コンポーネントは提供されていないので、自前で実装する必要があります。ただ、examples に 実装例 があるので(それなら提供しててくれよ...と思いつつ)これを参考に実装しました。

import { useMap, useMapsLibrary } from "@vis.gl/react-google-maps";

export const Polyline = ({ path, ...options }: google.maps.PolylineOptions) => {
  const map = useMap();
  const mapsLibrary = useMapsLibrary("maps");
  const [polyline, setPolyline] = useState<google.maps.Polyline | null>(null);

  // Polyline インスタンスを作成
  useEffect(() => {
    if (!mapsLibrary || polyline) return;
    setPolyline(new mapsLibrary.Polyline());
  }, [mapsLibrary, polyline]);

  // オプションとパスを適用
  useEffect(() => {
    if (!polyline) return;
    polyline.setOptions(options);
    polyline.setPath(path);
  }, [polyline, options, path]);

  // Map に紐付け
  useEffect(() => {
    if (!map || !polyline) return;
    polyline.setMap(map);
    return () => polyline.setMap(null);
  }, [map, polyline]);

  return null;
};

Polyline の描画では、他にもいくつか工夫ができます。

枠線の表示

Polyline には枠線を描画する機能がないですが、太い線と細い線を重ねることで擬似的に枠線を表現することが可能です。

<Polyline path={path} strokeColor="#FFFFFF" strokeWeight={6} />;
<Polyline path={path} strokeColor="#000000" strokeWeight={4} />;

点線の実装

Polyline は点線での表示も可能です。点線の表示は Google Maps の公式ドキュメントでも紹介されており、icons プロパティに Symbol を繰り返し配置することで実現できます。

const dashedLine = {
  path: "M 0,-1 0,1",
  strokeColor: "#9e9e9e",
  strokeWeight: 4,
  scale: 2,
};

<Polyline
  path={pastPath}
  strokeOpacity={0}
  icons={[{ icon: dashedLine, offset: "0", repeat: "12px" }]}
/>;

4. モバイルでのクリック競合問題

開発中に少しハマった問題もご紹介します。

ポリラインの上にマーカーを重ねて描画し、それぞれにクリックイベントを設定する場合、モバイル端末での挙動に注意が必要です。

HTML の通常の挙動であれば、上に描画されている要素(マーカー)のクリックイベントが優先されるはずです。しかし、ポリライン → マーカーの順でイベントが発火してしまい、マーカーをタップしたのにポリラインの処理が先に実行されてしまうことがありました。

これを解決するために useRef でフラグ管理しました。

const isMarkerClicked = useRef(false);

const handleMarkerClick = () => {
  isMarkerClicked.current = true;
  // マーカーの処理(詳細表示など)
};

const handlePolylineClick = () => {
  isMarkerClicked.current = false;

  // マーカークリックのイベントが発火するのを待ってから処理
  setTimeout(() => {
    if (!isMarkerClicked.current) {
      // ポリラインの処理
    }
  }, 50);
};

CSSpointer-events: none では解決できず、setTimeout でイベントの発火順を制御するというイマイチな対応になってしまいましたが、要件を満たすために仕方なくという感じでした。。

おわりに

React × Google Maps の実装で工夫した点を紹介しました。

@vis.gl/react-google-mapsGoogle が推奨するだけあって、React との親和性が高く、実装もスムーズでした。地図機能を実装する際の選択肢として、ぜひ検討してみてください。

同じように地図機能の実装に携わる方の参考になれば幸いです。

参考リンク