@react-google-maps/apiでReactに地図とマーカーを表示する
React アプリに Google Maps を組み込み、地図上にマーカーと情報ウィンドウを表示するところまでをハンズオン形式でまとめます。素の Google Maps JavaScript API を命令的に操作する方法もありますが、React コンポーネントとして宣言的に書ける @react-google-maps/api を使うと、state の変化にマーカーが追従する UI を素直に組めます。
この記事のゴール
- API キーを安全に読み込んで地図を表示する
- 複数マーカーを配列から描画する
- マーカークリックで情報ウィンドウ(InfoWindow)を出す
- 再レンダリングでのパフォーマンス劣化を避ける
環境
- React 18 系(Vite もしくは Create React App)
- TypeScript
@react-google-maps/api(執筆時点で広く使われている 2.x 系を想定)
npm install @react-google-maps/api
Google Cloud Console で「Maps JavaScript API」を有効化し、API キーを発行します。キーには必ずリファラー制限(本番ドメイン・localhost)を付けておきます。
最小構成:地図を表示する
useJsApiLoader で Maps のスクリプト読み込みを管理し、読み込み完了後に <GoogleMap> を描画します。
import { GoogleMap, useJsApiLoader } from "@react-google-maps/api";
const containerStyle = {
width: "100%",
height: "400px",
};
const center = {
lat: 35.681236, // 東京駅付近
lng: 139.767125,
};
export function SimpleMap() {
const { isLoaded } = useJsApiLoader({
id: "google-map-script",
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY as string,
});
if (!isLoaded) return <div>Loading...</div>;
return (
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
zoom={14}
/>
);
}
ポイントは3つです。
googleMapsApiKeyは環境変数から渡す。コードに直書きしないisLoadedがtrueになるまで<GoogleMap>を描画しない- コンテナには 明示的な高さ を指定する(高さが 0 だと地図が表示されない)
高さの指定漏れで「地図が真っ白/何も出ない」というのは最頻出のつまずきです。必ず数値の高さを与えてください。
環境変数の扱い
Vite の場合、.env.local に以下を書きます(値はダミー)。
# .env.local VITE_GOOGLE_MAPS_API_KEY=your_api_key_here
.env.local は .gitignore に入れてコミットしないこと。ただしフロントエンドのビルド成果物には API キーが最終的に埋め込まれるため、「秘密」にはできません。だからこそ Cloud Console 側のリファラー制限・API 制限が実質的な防御線 になります。
マーカーを1つ置く
import { GoogleMap, Marker, useJsApiLoader } from "@react-google-maps/api";
export function MapWithMarker() {
const { isLoaded } = useJsApiLoader({
id: "google-map-script",
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY as string,
});
if (!isLoaded) return <div>Loading...</div>;
return (
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={14}>
<Marker position={center} />
</GoogleMap>
);
}
複数マーカーを配列から描画する
実務では地点データの配列をマーカーにマッピングするのが基本形です。
type Place = {
id: string;
name: string;
position: { lat: number; lng: number };
};
const places: Place[] = [
{ id: "1", name: "地点A", position: { lat: 35.681236, lng: 139.767125 } },
{ id: "2", name: "地点B", position: { lat: 35.689487, lng: 139.691706 } },
{ id: "3", name: "地点C", position: { lat: 35.658034, lng: 139.701636 } },
];
export function MapWithMarkers() {
const { isLoaded } = useJsApiLoader({
id: "google-map-script",
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY as string,
});
if (!isLoaded) return <div>Loading...</div>;
return (
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={12}>
{places.map((place) => (
<Marker key={place.id} position={place.position} title={place.name} />
))}
</GoogleMap>
);
}
key には配列の index ではなく、データ側の安定した ID を使ってください。並び替えや追加削除があるとき index key はマーカーの取り違えを起こします。
クリックで InfoWindow を出す
選択中のマーカーを state で持ち、その位置に <InfoWindow> を描画します。
import { useState } from "react";
import {
GoogleMap,
Marker,
InfoWindow,
useJsApiLoader,
} from "@react-google-maps/api";
export function MapWithInfoWindow() {
const { isLoaded } = useJsApiLoader({
id: "google-map-script",
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY as string,
});
const [selectedId, setSelectedId] = useState<string | null>(null);
if (!isLoaded) return <div>Loading...</div>;
const selected = places.find((p) => p.id === selectedId);
return (
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={12}>
{places.map((place) => (
<Marker
key={place.id}
position={place.position}
title={place.name}
onClick={() => setSelectedId(place.id)}
/>
))}
{selected && (
<InfoWindow
position={selected.position}
onCloseClick={() => setSelectedId(null)}
>
<div>
<strong>{selected.name}</strong>
</div>
</InfoWindow>
)}
</GoogleMap>
);
}
「選択中の ID だけを state に持ち、描画時に配列から引き直す」構造にすると、選択オブジェクトそのものを state に持つより状態がシンプルになります。閉じるときは selectedId を null に戻すだけです。
パフォーマンスの注意点
<GoogleMap> に渡す center や mapContainerStyle をコンポーネント内のオブジェクトリテラルで毎回生成すると、レンダリングのたびに新しい参照になり、意図しない再描画やカメラ位置のリセットを招くことがあります。定数はコンポーネント外に出すか、useMemo で固定します。
import { useMemo } from "react";
const containerStyle = { width: "100%", height: "400px" }; // 外に出す
export function Map() {
const center = useMemo(() => ({ lat: 35.681236, lng: 139.767125 }), []);
// ...
}
また useJsApiLoader の id は固定文字列にします。id を変えるとスクリプトの二重読み込みの原因になります。
確認方法
npm run devで起動し、指定した高さの地図が表示されること- 3つのマーカーが地図上に出ること
- マーカークリックで InfoWindow が開き、×で閉じること
- ブラウザの Network タブで Maps のスクリプトが1回だけ読み込まれていること
- Console に
RefererNotAllowedMapError等が出ていないこと
よくあるエラーと対処
- 地図が真っ白:コンテナの高さ指定漏れ、またはキー未設定・API 未有効化を疑う
RefererNotAllowedMapError:Cloud Console のリファラー制限に現在のオリジン(http://localhost:5173など)を追加する- マーカーが大量(数百〜):
MarkerClustererでクラスタリングするか、Advanced Markers への移行を検討する - 課金:Maps JavaScript API は従量課金。無料枠と予算アラートを必ず設定しておく
まとめ
@react-google-maps/api を使うと、useJsApiLoader で読み込みを管理し、<GoogleMap> の子として <Marker> / <InfoWindow> を宣言的に並べるだけで地図 UI が作れます。つまずきどころは「コンテナの高さ」「環境変数とリファラー制限」「オブジェクト参照の再生成」の3点に集約されます。ここを押さえれば、地点データの可視化はすぐに実装できます。
